initial updates to api v2.0.0:
- switch access backend to access-manager-api - change resource quota to pool based - simplify backend system - various cleanup
This commit is contained in:
@@ -3,3 +3,4 @@
|
||||
**/localdb.json
|
||||
**/docs
|
||||
**/config.json
|
||||
.vscode/settings.json
|
||||
@@ -0,0 +1,21 @@
|
||||
### Get ticket
|
||||
POST {{baseUrl}}/access/ticket
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
username={{username}}
|
||||
&password={{password}}
|
||||
|
||||
### Get user
|
||||
GET {{baseUrl}}/access/users/{{username}}
|
||||
|
||||
### Get group
|
||||
GET {{baseUrl}}/access/groups/{{groupname}}
|
||||
|
||||
### Get all pools
|
||||
GET {{baseUrl}}/access/pools/
|
||||
|
||||
### Get pool
|
||||
GET {{baseUrl}}/access/pools/{{poolname}}
|
||||
|
||||
### Get all pools
|
||||
GET {{baseUrl}}/cluster/nodes
|
||||
@@ -0,0 +1,33 @@
|
||||
### Get ticket
|
||||
POST {{baseUrl}}/access/ticket
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
username={{username}}
|
||||
&password={{password}}
|
||||
|
||||
### Get instance resources
|
||||
GET {{baseUrl}}/cluster/{{testvmpath}}
|
||||
|
||||
### Test create instance
|
||||
|
||||
POST {{baseUrl}}/cluster/{{testvmpath}}/create
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
name=testvm
|
||||
&pool={{poolname}}
|
||||
&cores=8
|
||||
&memory=8192
|
||||
|
||||
### Test fail create instance
|
||||
|
||||
POST {{baseUrl}}/cluster/{{testvmpath}}/create
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
name=testvm
|
||||
&pool={{poolname}}
|
||||
&cores=9999
|
||||
&memory=8192
|
||||
|
||||
### Test delete instance
|
||||
|
||||
DELETE {{baseUrl}}/cluster/{{testvmpath}}/delete
|
||||
@@ -0,0 +1,2 @@
|
||||
### Get version
|
||||
GET {{baseUrl}}/version
|
||||
+10
-109
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"application": {
|
||||
"hostname": "paas.mydomain.example",
|
||||
"domain": "mydomain.example",
|
||||
"listenPort": 8081
|
||||
},
|
||||
"backends": {
|
||||
"pve": {
|
||||
"import": "pve.js",
|
||||
@@ -17,44 +22,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"localdb": {
|
||||
"import": "localdb.js",
|
||||
"access_manager": {
|
||||
"import": "access_manager.js",
|
||||
"config": {
|
||||
"dbfile": "localdb.json"
|
||||
}
|
||||
},
|
||||
"paasldap": {
|
||||
"import": "paasldap.js",
|
||||
"config": {
|
||||
"url": "http://paasldap.mydomain.example",
|
||||
"realm": "ldap"
|
||||
"url": "http://localhost:8083"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers": {
|
||||
"instance": {
|
||||
"pve": "pve"
|
||||
},
|
||||
"users": {
|
||||
"realm": {
|
||||
"pve": [
|
||||
"localdb"
|
||||
],
|
||||
"ldap": [
|
||||
"localdb",
|
||||
"paasldap"
|
||||
]
|
||||
},
|
||||
"any": [
|
||||
"localdb",
|
||||
"paasldap"
|
||||
]
|
||||
}
|
||||
},
|
||||
"application": {
|
||||
"hostname": "paas.mydomain.example",
|
||||
"domain": "mydomain.example",
|
||||
"listenPort": 8081
|
||||
"instance": "pve",
|
||||
"users": ["access_manager"]
|
||||
},
|
||||
"useriso": {
|
||||
"node": "examplenode1",
|
||||
@@ -160,81 +137,5 @@
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultuser": {
|
||||
"resources": {
|
||||
"cpu": {
|
||||
"global": [],
|
||||
"nodes": {}
|
||||
},
|
||||
"cores": {
|
||||
"global": {
|
||||
"max": 0
|
||||
},
|
||||
"nodes": {}
|
||||
},
|
||||
"memory": {
|
||||
"global": {
|
||||
"max": 0
|
||||
},
|
||||
"nodes": {}
|
||||
},
|
||||
"swap": {
|
||||
"global": {
|
||||
"max": 0
|
||||
},
|
||||
"nodes": {}
|
||||
},
|
||||
"local": {
|
||||
"global": {
|
||||
"max": 0
|
||||
},
|
||||
"nodes": {}
|
||||
},
|
||||
"cephpl": {
|
||||
"global": {
|
||||
"max": 0
|
||||
},
|
||||
"nodes": {}
|
||||
},
|
||||
"network": {
|
||||
"global": {
|
||||
"max": 0
|
||||
},
|
||||
"nodes": {}
|
||||
},
|
||||
"pci": {
|
||||
"global": [],
|
||||
"nodes": {}
|
||||
}
|
||||
},
|
||||
"nodes": [],
|
||||
"cluster": {
|
||||
"vmid": {
|
||||
"min": -1,
|
||||
"max": -1
|
||||
},
|
||||
"pool": ""
|
||||
},
|
||||
"templates": {
|
||||
"instances": {
|
||||
"lxc": {},
|
||||
"qemu": {}
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"lxc": {
|
||||
"type": "veth",
|
||||
"bridge": "vmbr0",
|
||||
"vlan": 10,
|
||||
"ip": "dhcp",
|
||||
"ip6": "dhcp"
|
||||
},
|
||||
"qemu": {
|
||||
"type": "virtio",
|
||||
"bridge": "vmbr0",
|
||||
"vlan": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
{
|
||||
"users": {
|
||||
"exampleuser@auth": {
|
||||
"resources": {
|
||||
"cpu": {
|
||||
"global": [
|
||||
{
|
||||
"match": "kvm64",
|
||||
"name": "kvm64",
|
||||
"max": 1
|
||||
},
|
||||
{
|
||||
"match": "host",
|
||||
"name": "host",
|
||||
"max": 1
|
||||
}
|
||||
],
|
||||
"nodes": {}
|
||||
},
|
||||
"cores": {
|
||||
"global": {
|
||||
"max": 128
|
||||
},
|
||||
"nodes": {}
|
||||
},
|
||||
"memory": {
|
||||
"global": {
|
||||
"max": 137438953472
|
||||
},
|
||||
"nodes": {}
|
||||
},
|
||||
"swap": {
|
||||
"global": {
|
||||
"max": 137438953472
|
||||
},
|
||||
"nodes": {}
|
||||
},
|
||||
"local": {
|
||||
"global": {
|
||||
"max": 1099511627776
|
||||
},
|
||||
"nodes": {}
|
||||
},
|
||||
"cephpl": {
|
||||
"global": {
|
||||
"max": 1099511627776
|
||||
},
|
||||
"nodes": {}
|
||||
},
|
||||
"network": {
|
||||
"global": {
|
||||
"max": 100000
|
||||
},
|
||||
"nodes": {}
|
||||
},
|
||||
"pci": {
|
||||
"global": [],
|
||||
"nodes": {
|
||||
"example-node-0": [
|
||||
{
|
||||
"match": "[device 1]",
|
||||
"name": "Device 1",
|
||||
"max": 1
|
||||
},
|
||||
{
|
||||
"match": "[device 2]",
|
||||
"name": "Device 2",
|
||||
"max": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"cluster": {
|
||||
"admin": false,
|
||||
"nodes": {
|
||||
"example-node-0": true,
|
||||
"example-node-1": true,
|
||||
"example-node-2": true
|
||||
},
|
||||
"vmid": {
|
||||
"min": 100,
|
||||
"max": 199
|
||||
},
|
||||
"pools": {
|
||||
"example-pool-1": true,
|
||||
"example-pool-2": true
|
||||
},
|
||||
"backups": {
|
||||
"max": 5
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"instances": {
|
||||
"lxc": {
|
||||
"net0": {
|
||||
"value": "name=eth0,bridge=vmbr0,ip=dhcp,ip6=dhcp,tag=10,type=veth,rate=1000",
|
||||
"resource": {
|
||||
"name": "network",
|
||||
"amount": 1000
|
||||
}
|
||||
}
|
||||
},
|
||||
"qemu": {
|
||||
"cpu": {
|
||||
"value": "host",
|
||||
"resource": null
|
||||
},
|
||||
"machine": {
|
||||
"value": "q35",
|
||||
"resource": null
|
||||
},
|
||||
"net0": {
|
||||
"value": "virtio,bridge=vmbr0,tag=10,rate=1000",
|
||||
"resource": {
|
||||
"name": "network",
|
||||
"amount": 1000
|
||||
}
|
||||
},
|
||||
"scsihw": {
|
||||
"value": "virtio-scsi-single",
|
||||
"resource": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"lxc": {
|
||||
"type": "veth",
|
||||
"bridge": "vmbr0",
|
||||
"vlan": 10,
|
||||
"ip": "dhcp",
|
||||
"ip6": "dhcp"
|
||||
},
|
||||
"qemu": {
|
||||
"type": "virtio",
|
||||
"bridge": "vmbr0",
|
||||
"vlan": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ export default defineConfig([{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"caughtErrorsIgnorePattern": "^_"
|
||||
}]
|
||||
}],
|
||||
"prefer-const": ["error"]
|
||||
}
|
||||
}]);
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "proxmoxaas-api",
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.0",
|
||||
"description": "REST API for ProxmoxAAS",
|
||||
"main": "src/main.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
import axios from "axios";
|
||||
import { ACCESS_BACKEND } from "./backends.js";
|
||||
import * as setCookie from "set-cookie-parser";
|
||||
|
||||
export default class ACCESS_MANAGER_API extends ACCESS_BACKEND {
|
||||
#apiURL = null;
|
||||
|
||||
constructor (config) {
|
||||
super();
|
||||
this.#apiURL = config.url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send HTTP request to paas-LDAP API.
|
||||
* @param {*} path HTTP path, prepended with the paas-LDAP API base url
|
||||
* @param {*} method HTTP method
|
||||
* @param {*} auth HTTP auth cookies
|
||||
* @param {*} body body parameters and data to be sent. Optional.
|
||||
* @returns {Object} HTTP response object
|
||||
*/
|
||||
async #requestAPI (path, method, auth = null, body = null) {
|
||||
const url = `${this.#apiURL}${path}`;
|
||||
const content = {
|
||||
method,
|
||||
mode: "cors",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
data: body
|
||||
};
|
||||
|
||||
if (auth) {
|
||||
content.headers.Cookie = `PAASAccessManagerTicket=${auth.PAASAccessManagerTicket};`;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await axios.request(url, content);
|
||||
return {
|
||||
ok: result.status === 200,
|
||||
status: result.status,
|
||||
data: result.data,
|
||||
headers: result.headers
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.log(`access: error ocuured in access.requestAPI: ${error}`);
|
||||
const result = error.response;
|
||||
result.ok = result.status === 200;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
async openSession (user, password) {
|
||||
const credentials = { username: `${user.id}@${user.realm}`, password };
|
||||
const response = await this.#requestAPI("/ticket", "POST", null, credentials);
|
||||
if (response.ok) {
|
||||
const cookies = setCookie.parse(response.headers["set-cookie"]);
|
||||
cookies.forEach((e) => {
|
||||
e.expiresMSFromNow = e.expires - Date.now();
|
||||
});
|
||||
return {
|
||||
ok: true,
|
||||
status: response.status,
|
||||
message: "",
|
||||
cookies
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
ok: false,
|
||||
status: response.status,
|
||||
message: response.data.error,
|
||||
cookies: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async closeSession(tokens) {
|
||||
const response = await this.#requestAPI("/ticket", "DELETE", tokens);
|
||||
return response;
|
||||
}
|
||||
|
||||
async addUser (user, attributes, params) {}
|
||||
|
||||
async getUser (user, params) {
|
||||
const response = await this.#requestAPI(`/users/${user.id}@${user.realm}`, "GET", params);
|
||||
if (response.ok) { // if ok, return user data
|
||||
return {
|
||||
ok: true,
|
||||
status: response.status,
|
||||
user: response.data.user
|
||||
};
|
||||
}
|
||||
else { // else return null
|
||||
return {
|
||||
ok: false,
|
||||
status: response.status,
|
||||
message: response.data.error
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async setUser (user, attributes, params) {}
|
||||
|
||||
async delUser (user, params) {}
|
||||
|
||||
async addGroup (group, attributes, params) {}
|
||||
|
||||
async getGroup (group, params) {
|
||||
const response = await this.#requestAPI(`/groups/${group.id}-${group.realm}`, "GET", params);
|
||||
if (response.ok) { // if ok, return user data
|
||||
return {
|
||||
ok: true,
|
||||
status: response.status,
|
||||
group: response.data.group
|
||||
};
|
||||
}
|
||||
else { // else return null
|
||||
return {
|
||||
ok: false,
|
||||
status: response.status,
|
||||
message: response.data.error
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async setGroup (group, attributes, params) {}
|
||||
|
||||
async delGroup (group, attributes, params) {}
|
||||
|
||||
async addUserToGroup (user, group, params) {}
|
||||
|
||||
async delUserFromGroup (user, group, params) {}
|
||||
|
||||
async addPool (pool, attributes, params) {}
|
||||
|
||||
async getPool (pool, params) {
|
||||
const response = await this.#requestAPI(`/pools/${pool}`, "GET", params);
|
||||
if (response.ok) { // if ok, return user data
|
||||
return {
|
||||
ok: true,
|
||||
status: response.status,
|
||||
pool: response.data.pool
|
||||
};
|
||||
}
|
||||
else { // else return null
|
||||
return {
|
||||
ok: false,
|
||||
status: response.status,
|
||||
message: response.data.error
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async setPool (pool, attributes, params) {}
|
||||
|
||||
async delPool (pool, params) {}
|
||||
|
||||
async addGroupToPool (group, pool, params) {}
|
||||
|
||||
async delGroupFromPool (group, pool, params) {}
|
||||
}
|
||||
+115
-183
@@ -17,43 +17,10 @@ export default async () => {
|
||||
global.backends[name] = new Backend(config);
|
||||
console.log(`backends: initialized backend ${name} from ${importPath}`);
|
||||
}
|
||||
global.pve = global.backends[global.config.handlers.instance.pve];
|
||||
global.userManager = new USER_BACKEND_MANAGER(global.config.handlers.users);
|
||||
global.pve = global.backends[global.config.handlers.instance];
|
||||
global.access = global.backends[global.config.handlers.users];
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for all backend types. Contains only two methods for opening and closing a session with the backend.
|
||||
* Users will recieve tokens from all backends when first authenticating and will delete tokens when logging out.
|
||||
*/
|
||||
class BACKEND {
|
||||
/**
|
||||
* Opens a session with the backend and creates session tokens if needed
|
||||
* @param {{id: string, realm: string}} user object containing id and realm
|
||||
* @param {string} password
|
||||
* @returns {{ok: boolean, status: number, message: string, cookies: {name: string, value: string}[]}} response like object with list of session token objects with token name and value
|
||||
*/
|
||||
openSession (user, password) {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
message: "",
|
||||
cookies: []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes an opened session with the backend if needed
|
||||
* @param {{name: string, value: string}[]} token list of session token objects with token name and value, may include irrelevant tokens for a specific backend
|
||||
* @returns {boolean} true if session was closed successfully, false otherwise
|
||||
*/
|
||||
closeSession (tokens) {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class AtomicChange {
|
||||
constructor (valid, delta, callback, status = { ok: true, status: 200, message: "" }) {
|
||||
this.valid = valid;
|
||||
@@ -76,10 +43,43 @@ export function doNothingCallback (delta) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for backend types that store/interact with user & group data.
|
||||
* Interface for all backend types. Contains only two methods for opening and closing a session with the backend.
|
||||
* Users will recieve tokens from all backends when first authenticating and will delete tokens when logging out.
|
||||
*/
|
||||
export class BACKEND {
|
||||
/**
|
||||
* Opens a session with the backend and creates session tokens if needed
|
||||
* @param {{id: string, realm: string}} user object containing id and realm
|
||||
* @param {string} password
|
||||
* @returns {{ok: boolean, status: number, message: string, cookies: {name: string, value: string}[]}} response like object with list of session token objects with token name and value
|
||||
*/
|
||||
async openSession (user, password) {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
message: "",
|
||||
cookies: []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes an opened session with the backend if needed
|
||||
* @param {{name: string, value: string}[]} token list of session token objects with token name and value, may include irrelevant tokens for a specific backend
|
||||
* @returns {boolean} true if session was closed successfully, false otherwise
|
||||
*/
|
||||
async closeSession (tokens) {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for backend types that store/interact with user, group, and pool data.
|
||||
* Not all backends need to implement all interface methods.
|
||||
*/
|
||||
class USER_BACKEND extends BACKEND {
|
||||
export class ACCESS_BACKEND extends BACKEND {
|
||||
/**
|
||||
* Validate an add user operation with the following parameters.
|
||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
||||
@@ -88,7 +88,7 @@ class USER_BACKEND extends BACKEND {
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {AtomicChange} atomic change object
|
||||
*/
|
||||
addUser (user, attributes, params) {}
|
||||
async addUser (user, attributes, params) {}
|
||||
|
||||
/**
|
||||
* Get user from backend
|
||||
@@ -96,14 +96,7 @@ class USER_BACKEND extends BACKEND {
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {Object} containing user data from this backend, null if user does not exist
|
||||
*/
|
||||
getUser (user, params) {}
|
||||
|
||||
/**
|
||||
* Get all users from backend
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {Array} containing each user data from this backend
|
||||
*/
|
||||
getAllUsers (params) {}
|
||||
async getUser (user, params) {}
|
||||
|
||||
/**
|
||||
* Validate a set user operation with the following parameters.
|
||||
@@ -113,7 +106,7 @@ class USER_BACKEND extends BACKEND {
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {AtomicChange} atomic change object
|
||||
*/
|
||||
setUser (user, attributes, params) {}
|
||||
async setUser (user, attributes, params) {}
|
||||
|
||||
/**
|
||||
* Validate a delete user operation with the following parameters.
|
||||
@@ -122,7 +115,7 @@ class USER_BACKEND extends BACKEND {
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {AtomicChange} atomic change object
|
||||
*/
|
||||
delUser (user, params) {}
|
||||
async delUser (user, params) {}
|
||||
|
||||
/**
|
||||
* Validate an add group operation with the following parameters.
|
||||
@@ -132,22 +125,15 @@ class USER_BACKEND extends BACKEND {
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {AtomicChange} atomic change object
|
||||
*/
|
||||
addGroup (group, attributes, params) {}
|
||||
async addGroup (group, attributes, params) {}
|
||||
|
||||
/**
|
||||
* Get group from backend
|
||||
* @param {{id: string}} group
|
||||
* @param {{id: string, realm: string}} group
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {Object} containing group data from this backend, null if user does not exist
|
||||
*/
|
||||
getGroup (group, params) {}
|
||||
|
||||
/**
|
||||
* Get all users from backend
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {Array} containing each group data from this backend
|
||||
*/
|
||||
getAllGroups (params) {}
|
||||
async getGroup (group, params) {}
|
||||
|
||||
/**
|
||||
* Validate a set group operation with the following parameters.
|
||||
@@ -157,7 +143,8 @@ class USER_BACKEND extends BACKEND {
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {AtomicChange} atomic change object
|
||||
*/
|
||||
setGroup (group, attributes, params) {}
|
||||
async setGroup (group, attributes, params) {}
|
||||
|
||||
/**
|
||||
* Validate a del group operation with the following parameters.
|
||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
||||
@@ -165,27 +152,84 @@ class USER_BACKEND extends BACKEND {
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {AtomicChange} atomic change object
|
||||
*/
|
||||
delGroup (group, attributes, params) {}
|
||||
async delGroup (group, attributes, params) {}
|
||||
|
||||
/**
|
||||
* Validate an add user to group operation with the following parameters.
|
||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
||||
* @param {{id: string, realm: string}} user
|
||||
* @param {{id: string}} group
|
||||
* @param {{id: string, realm: string}} group
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {AtomicChange} atomic change object
|
||||
*/
|
||||
addUserToGroup (user, group, params) {}
|
||||
async addUserToGroup (user, group, params) {}
|
||||
|
||||
/**
|
||||
* Validate a remove user from group operation with the following parameters.
|
||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
||||
* @param {{id: string, realm: string}} user
|
||||
* @param {{id: string}} group
|
||||
* @param {{id: string, realm: string}} group
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {AtomicChange} atomic change object
|
||||
*/
|
||||
delUserFromGroup (user, group, params) {}
|
||||
async delUserFromGroup (user, group, params) {}
|
||||
|
||||
/**
|
||||
* Validate an add pool operation with the following parameters.
|
||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
||||
* @param {{id: string, realm: string}} pool
|
||||
* @param {Object} attributes pool attributes
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {AtomicChange} atomic change object
|
||||
*/
|
||||
async addPool (pool, attributes, params) {}
|
||||
|
||||
/**
|
||||
* Get pool from backend
|
||||
* @param {string} pool
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {Object} containing pool data from this backend, null if poll does not exist
|
||||
*/
|
||||
async getPool (pool, params) {}
|
||||
|
||||
/**
|
||||
* Validate a set pool operation with the following parameters.
|
||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
||||
* @param {string} pool
|
||||
* @param {Object} attributes pool attributes
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {AtomicChange} atomic change object
|
||||
*/
|
||||
async setPool (pool, attributes, params) {}
|
||||
|
||||
/**
|
||||
* Validate a del pool operation with the following parameters.
|
||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
||||
* @param {string} pool
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {AtomicChange} atomic change object
|
||||
*/
|
||||
async delPool (pool, params) {}
|
||||
|
||||
/**
|
||||
* Validate an add group to pool operation with the following parameters.
|
||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
||||
* @param {{id: string, realm: string}} group
|
||||
* @param {string} pool
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {AtomicChange} atomic change object
|
||||
*/
|
||||
async addGroupToPool (group, pool, params) {}
|
||||
|
||||
/**
|
||||
* Validate a remove group from pool operation with the following parameters.
|
||||
* Returns whether the change is valid and a delta object to be used in the operation.
|
||||
* @param {{id: string, realm: string}} group
|
||||
* @param {string} pool
|
||||
* @param {Object} params authentication params, usually req.cookies
|
||||
* @returns {AtomicChange} atomic change object
|
||||
*/
|
||||
async delGroupFromPool (group, pool, params) {}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,13 +242,13 @@ export class PVE_BACKEND extends BACKEND {
|
||||
* @param {string} node node id
|
||||
* @returns {}
|
||||
*/
|
||||
getNode (node) {}
|
||||
async getNode (node) {}
|
||||
|
||||
/**
|
||||
* Send a signal to synchronize a node after some change has been made.
|
||||
* * @param {string} node node id
|
||||
*/
|
||||
syncNode (node) {}
|
||||
async syncNode (node) {}
|
||||
|
||||
/**
|
||||
* Get and return instance data.
|
||||
@@ -213,14 +257,14 @@ export class PVE_BACKEND extends BACKEND {
|
||||
* @param {string} type instance type
|
||||
* @param {string} vmid instance id
|
||||
*/
|
||||
getInstance (node, type, instance) {}
|
||||
async getInstance (node, type, instance) {}
|
||||
|
||||
/**
|
||||
* Send a signal to synchronize an instance after some change has been made.
|
||||
* @param {string} node node id
|
||||
* @param {string} instance instance id
|
||||
*/
|
||||
syncInstance (node, instance) {}
|
||||
async syncInstance (node, instance) {}
|
||||
|
||||
/**
|
||||
* Get meta data for a specific disk. Adds info that is not normally available in a instance's config.
|
||||
@@ -250,122 +294,10 @@ export class PVE_BACKEND extends BACKEND {
|
||||
async getDevice (node, instance, deviceid) {}
|
||||
|
||||
/**
|
||||
* Get user resource data including used, available, and maximum resources.
|
||||
* @param {{id: string, realm: string}} user object of user to get resource data.
|
||||
* Get pool resource data including used, available, and maximum resources.
|
||||
* @param {string} pool
|
||||
* @param {Object} cookies object containing k-v store of cookies
|
||||
* @returns {{used: Object, avail: Object, max: Object, resources: Object}} used, available, maximum, and resource metadata for the specified user.
|
||||
*/
|
||||
getUserResources (user, cookies) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for user database backends.
|
||||
*/
|
||||
export class DB_BACKEND extends USER_BACKEND {}
|
||||
|
||||
/**
|
||||
* Interface for user auth backends.
|
||||
*/
|
||||
export class AUTH_BACKEND extends USER_BACKEND {}
|
||||
|
||||
/**
|
||||
* Interface combining all user backends into a single interface
|
||||
* Calling methods will also call sub handler methods
|
||||
*/
|
||||
class USER_BACKEND_MANAGER extends USER_BACKEND {
|
||||
#config = null;
|
||||
|
||||
constructor (config) {
|
||||
super();
|
||||
this.#config = config;
|
||||
}
|
||||
|
||||
getBackendsByUser (user) {
|
||||
if (user != null) {
|
||||
return this.#config.realm[user.realm];
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
addUser (user, attributes, params) {}
|
||||
|
||||
async getUser (user, params) {
|
||||
let userData = {};
|
||||
for (const backend of this.#config.realm[user.realm]) {
|
||||
const backendData = await global.backends[backend].getUser(user, params);
|
||||
if (backendData) {
|
||||
userData = { ...backendData, ...userData };
|
||||
}
|
||||
}
|
||||
return userData;
|
||||
}
|
||||
|
||||
async getAllUsers (params) {
|
||||
const userData = {};
|
||||
for (const backend of this.#config.any) {
|
||||
const backendData = await global.backends[backend].getAllUsers(params);
|
||||
if (backendData) {
|
||||
for (const user of Object.keys(backendData)) {
|
||||
userData[user] = { ...backendData[user], ...userData[user] };
|
||||
}
|
||||
}
|
||||
}
|
||||
return userData;
|
||||
}
|
||||
|
||||
async setUser (user, attributes, params) {
|
||||
const atomicChanges = [];
|
||||
for (const backend of this.#config.realm[user.realm]) {
|
||||
const atomicChange = await global.backends[backend].setUser(user, attributes, params);
|
||||
if (atomicChange.valid === false) { // if any fails, preemptively exit
|
||||
return atomicChange.status;
|
||||
}
|
||||
atomicChanges.push(atomicChange); // queue callback into array
|
||||
}
|
||||
const response = {
|
||||
ok: true,
|
||||
status: 200,
|
||||
message: "",
|
||||
allResponses: []
|
||||
};
|
||||
for (const atomicChange of atomicChanges) {
|
||||
const atomicResponse = await atomicChange.commit();
|
||||
if (atomicResponse.ok === false) {
|
||||
response.ok = false;
|
||||
response.status = atomicResponse.status;
|
||||
response.message = atomicResponse.message;
|
||||
}
|
||||
response.allResponses.push(); // execute callback
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
delUser (user, params) {}
|
||||
|
||||
addGroup (group, attributes, params) {}
|
||||
|
||||
getGroup (group, params) {}
|
||||
|
||||
async getAllGroups (params) {
|
||||
const groupData = {};
|
||||
for (const backend of this.#config.any) {
|
||||
const backendData = await global.backends[backend].getAllGroups(params);
|
||||
if (backendData) {
|
||||
for (const group of Object.keys(backendData)) {
|
||||
groupData[group] = { ...backendData[group], ...groupData[group] };
|
||||
}
|
||||
}
|
||||
}
|
||||
return groupData;
|
||||
}
|
||||
|
||||
setGroup (group, attributes, params) {}
|
||||
|
||||
delGroup (group, params) {}
|
||||
|
||||
addUserToGroup (user, group, params) {}
|
||||
|
||||
delUserFromGroup (user, group, params) {}
|
||||
async getPoolResources (user, cookies) {}
|
||||
}
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
import { readFileSync, writeFileSync } from "fs";
|
||||
import { exit } from "process";
|
||||
import { AtomicChange, DB_BACKEND, doNothingCallback } from "./backends.js";
|
||||
|
||||
export default class LocalDB extends DB_BACKEND {
|
||||
#path = null;
|
||||
#data = null;
|
||||
//#defaultuser = null;
|
||||
|
||||
constructor (config) {
|
||||
super();
|
||||
const path = config.dbfile;
|
||||
try {
|
||||
this.#path = path;
|
||||
this.#load();
|
||||
//this.#defaultuser = global.config.defaultuser;
|
||||
}
|
||||
catch {
|
||||
console.log(`error: ${path} was not found. Please follow the directions in the README to initialize localdb.json.`);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load db from local file system. Reads from file path store in path.
|
||||
*/
|
||||
#load () {
|
||||
this.#data = JSON.parse(readFileSync(this.#path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Save db to local file system. Saves to file path stored in path.
|
||||
*/
|
||||
#save () {
|
||||
writeFileSync(this.#path, JSON.stringify(this.#data));
|
||||
}
|
||||
|
||||
addUser (user, attributes, params) {}
|
||||
|
||||
getUser (user, params) {
|
||||
const requestedUser = `${user.id}@${user.realm}`;
|
||||
const requestingUser = params.username; // assume checkAuth has been run, which already checks that username matches PVE token
|
||||
// user can access a user's db data if they are an admin OR are requesting own data
|
||||
const authorized = this.#data.users[requestingUser].cluster.admin || requestingUser === requestedUser;
|
||||
if (authorized && this.#data.users[requestedUser]) {
|
||||
return this.#data.users[requestedUser];
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getAllUsers (params) {
|
||||
const requestingUser = params.username; // assume checkAuth has been run, which already checks that username matches PVE token
|
||||
if (this.#data.users[requestingUser].cluster.admin === true) {
|
||||
return this.#data.users;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
setUser (user, attributes, params) {
|
||||
if (attributes.resources && attributes.cluster && attributes.templates) {
|
||||
const username = `${user.id}@${user.realm}`;
|
||||
if (this.#data.users[username]) {
|
||||
if (this.#data.users[params.username] && this.#data.users[params.username].cluster.admin) {
|
||||
return new AtomicChange(false,
|
||||
{
|
||||
username,
|
||||
attributes: {
|
||||
resources: attributes.resources,
|
||||
cluster: attributes.cluster,
|
||||
templates: attributes.templates
|
||||
}
|
||||
},
|
||||
(delta) => {
|
||||
this.#data.users[delta.username] = delta.attributes;
|
||||
this.#save();
|
||||
return { ok: true, status: 200, message: "" };
|
||||
},
|
||||
{ ok: true, status: 200, message: "" }
|
||||
);
|
||||
}
|
||||
else {
|
||||
return new AtomicChange(false, {}, doNothingCallback, { ok: false, status: 401, message: `${params.username} is not an admin user in localdb` });
|
||||
}
|
||||
}
|
||||
else {
|
||||
return new AtomicChange(false, {}, doNothingCallback, { ok: false, status: 400, message: `${username} was not found in localdb` });
|
||||
}
|
||||
}
|
||||
else {
|
||||
return new AtomicChange(true, {}, doNothingCallback, null);
|
||||
}
|
||||
}
|
||||
|
||||
delUser (user, params) {}
|
||||
|
||||
// group methods not implemented because db backend does not store groups
|
||||
addGroup (group, atrributes, params) {}
|
||||
getGroup (group, params) {}
|
||||
getAllGroups (params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
setGroup (group, attributes, params) {}
|
||||
delGroup (group, params) {}
|
||||
|
||||
// assume that adding to group also adds to group's pool
|
||||
addUserToGroup (user, group, params) {}
|
||||
|
||||
// assume that adding to group also adds to group's pool
|
||||
delUserFromGroup (user, group, params) {}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
import axios from "axios";
|
||||
import { AtomicChange, AUTH_BACKEND, doNothingCallback } from "./backends.js";
|
||||
import * as setCookie from "set-cookie-parser";
|
||||
|
||||
export default class PAASLDAP extends AUTH_BACKEND {
|
||||
#url = null;
|
||||
#realm = null;
|
||||
|
||||
constructor (config) {
|
||||
super();
|
||||
this.#url = config.url;
|
||||
this.#realm = config.realm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send HTTP request to paas-LDAP API.
|
||||
* @param {*} path HTTP path, prepended with the paas-LDAP API base url
|
||||
* @param {*} method HTTP method
|
||||
* @param {*} body body parameters and data to be sent. Optional.
|
||||
* @returns {Object} HTTP response object
|
||||
*/
|
||||
async #request (path, method, auth = null, body = null) {
|
||||
const url = `${this.#url}${path}`;
|
||||
const content = {
|
||||
method,
|
||||
mode: "cors",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
data: body
|
||||
};
|
||||
|
||||
if (auth) {
|
||||
content.headers.Cookie = `PAASLDAPAuthTicket=${auth.PAASLDAPAuthTicket};`;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await axios.request(url, content);
|
||||
result.ok = result.status === 200;
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
const result = error.response;
|
||||
result.ok = result.status === 200;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
#handleGenericReturn (res) {
|
||||
return {
|
||||
ok: res.ok,
|
||||
status: res.status,
|
||||
message: res.ok ? "" : res.data.error
|
||||
};
|
||||
}
|
||||
|
||||
async openSession (user, password) {
|
||||
const username = user.id;
|
||||
const content = { username, password };
|
||||
const result = await this.#request("/ticket", "POST", null, content);
|
||||
if (result.ok) {
|
||||
const cookies = setCookie.parse(result.headers["set-cookie"]);
|
||||
cookies.forEach((e) => {
|
||||
e.expiresMSFromNow = e.expires - Date.now();
|
||||
});
|
||||
return {
|
||||
ok: true,
|
||||
status: result.status,
|
||||
message: "",
|
||||
cookies
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
ok: false,
|
||||
status: result.status,
|
||||
message: result.data.error,
|
||||
cookies: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async addUser (user, attributes, params) {}
|
||||
|
||||
async getUser (user, params) {
|
||||
if (!params) { // params required, do nothing if params are missing
|
||||
return null;
|
||||
}
|
||||
const res = await this.#request(`/users/${user.id}`, "GET", params);
|
||||
if (res.ok) { // if ok, return user data
|
||||
return res.data.user;
|
||||
}
|
||||
else { // else return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getAllUsers (params) {
|
||||
if (!params) {
|
||||
return null;
|
||||
}
|
||||
const res = await this.#request("/users", "GET", params);
|
||||
if (res.ok) { // if ok, return user data
|
||||
const users = res.data.users;
|
||||
const usersFormatted = {};
|
||||
// label each user object by user@realm
|
||||
for (const user of users) {
|
||||
usersFormatted[`${user.attributes.uid}@${this.#realm}`] = user;
|
||||
}
|
||||
return usersFormatted;
|
||||
}
|
||||
else { // else return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async setUser (user, attributes, params) {
|
||||
if (!attributes.userpassword && !attributes.cn && attributes.sn) {
|
||||
return new AtomicChange(true, {}, doNothingCallback, null); // change has no ldap attributes
|
||||
}
|
||||
const ldapAttributes = {};
|
||||
if (attributes.userpassword) {
|
||||
ldapAttributes.userpassword = attributes.userpassword;
|
||||
}
|
||||
if (attributes.cn) {
|
||||
ldapAttributes.cn = attributes.cn;
|
||||
}
|
||||
if (attributes.sn) {
|
||||
ldapAttributes.sn = attributes.sn;
|
||||
}
|
||||
return new AtomicChange(
|
||||
true,
|
||||
{
|
||||
user,
|
||||
ldapAttributes,
|
||||
params
|
||||
},
|
||||
async (delta) => {
|
||||
const res = await this.#request(`/users/${delta.user.id}`, "POST", delta.params, delta.ldapAttributes);
|
||||
return this.#handleGenericReturn(res);
|
||||
},
|
||||
{ ok: true, status: 200, message: "" }
|
||||
);
|
||||
}
|
||||
|
||||
async delUser (user, params) {}
|
||||
|
||||
async addGroup (group, attributes, params) {}
|
||||
|
||||
async getGroup (group, params) {
|
||||
return await this.#request(`/groups/${group.id}`, "GET", params);
|
||||
}
|
||||
|
||||
async getAllGroups (params) {
|
||||
if (!params) {
|
||||
return null;
|
||||
}
|
||||
const res = await this.#request("/groups", "GET", params);
|
||||
if (res.ok) { // if ok, return user data
|
||||
const groups = res.data.groups;
|
||||
const groupsFormatted = {};
|
||||
// label each user object by user@realm
|
||||
for (const group of groups) {
|
||||
groupsFormatted[`${group.attributes.cn}@${this.#realm}`] = group;
|
||||
}
|
||||
return groupsFormatted;
|
||||
}
|
||||
else { // else return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async setGroup (group, attributes, params) {
|
||||
// not implemented, LDAP groups do not have any attributes to change
|
||||
return new AtomicChange(true, {}, doNothingCallback, null); ;
|
||||
}
|
||||
|
||||
async delGroup (group, params) {}
|
||||
|
||||
async addUserToGroup (user, group, params) {}
|
||||
|
||||
async delUserFromGroup (user, group, params) {}
|
||||
}
|
||||
+77
-59
@@ -26,8 +26,8 @@ export default class PVE extends PVE_BACKEND {
|
||||
cookies: []
|
||||
};
|
||||
}
|
||||
const ticket = response.data.data.ticket;
|
||||
const csrftoken = response.data.data.CSRFPreventionToken;
|
||||
const ticket = response.data.ticket;
|
||||
const csrftoken = response.data.CSRFPreventionToken;
|
||||
return {
|
||||
ok: true,
|
||||
status: response.status,
|
||||
@@ -66,73 +66,39 @@ export default class PVE extends PVE_BACKEND {
|
||||
data: body
|
||||
};
|
||||
|
||||
if (auth && auth.cookies) {
|
||||
if (auth && auth.cookies) { // user cookie credentials
|
||||
content.headers.CSRFPreventionToken = auth.cookies.CSRFPreventionToken;
|
||||
content.headers.Cookie = `PVEAuthCookie=${auth.cookies.PVEAuthCookie}; CSRFPreventionToken=${auth.cookies.CSRFPreventionToken}`;
|
||||
}
|
||||
else if (auth && auth.token) {
|
||||
else if (auth && auth.token) { // upgraded request as api
|
||||
const token = this.#pveAPIToken;
|
||||
content.headers.Authorization = `PVEAPIToken=${token.user}@${token.realm}!${token.id}=${token.uuid}`;
|
||||
}
|
||||
else if (auth && auth.root) {
|
||||
const rootauth = await global.pve.requestPVE("/access/ticket", "POST", null, this.#pveRoot);
|
||||
else if (auth && auth.root) { // upgraded request as root
|
||||
const rootauth = await this.requestPVE("/access/ticket", "POST", null, this.#pveRoot);
|
||||
if (!(rootauth.status === 200)) {
|
||||
return rootauth.response;
|
||||
}
|
||||
const rootcookie = rootauth.data.data.ticket;
|
||||
const rootcsrf = rootauth.data.data.CSRFPreventionToken;
|
||||
const rootcookie = rootauth.data.ticket;
|
||||
const rootcsrf = rootauth.data.CSRFPreventionToken;
|
||||
content.headers.CSRFPreventionToken = rootcsrf;
|
||||
content.headers.Cookie = `PVEAuthCookie=${rootcookie}; CSRFPreventionToken=${rootcsrf}`;
|
||||
}
|
||||
|
||||
try {
|
||||
return await axios.request(url, content);
|
||||
const result = await axios.request(url, content);
|
||||
return {
|
||||
ok: result.ok,
|
||||
status: result.status,
|
||||
data: result.data.data, // pve returns {data: {data: {...}}}, unwrap here to conform to standard {data: {...}} format
|
||||
headers: result.headers
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.log(`backends: error ocuured in pve.requestPVE: ${error}`);
|
||||
return error.response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle various proxmox API responses. Handles sync and async responses.
|
||||
* In sync responses, responses are completed when the response arrives. Method returns the response directly.
|
||||
* In async responses, proxmox sends responses with a UPID to track process completion. Method returns the status of the proxmox process once it completes.
|
||||
* @param {string} node response originates from.
|
||||
* @param {Object} result response from proxmox.
|
||||
* @param {Object} res response object of ProxmoxAAS API call.
|
||||
*/
|
||||
async handleResponse (node, result, res) {
|
||||
const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay));
|
||||
if (result.status !== 200) {
|
||||
res.status(result.status).send({ error: result.statusText });
|
||||
res.end();
|
||||
}
|
||||
else if (result.data.data && typeof (result.data.data) === "string" && result.data.data.startsWith("UPID:")) {
|
||||
const upid = result.data.data;
|
||||
let taskStatus = await this.requestPVE(`/nodes/${node}/tasks/${upid}/status`, "GET", { token: true });
|
||||
while (taskStatus.data.data.status !== "stopped") {
|
||||
await waitFor(100);
|
||||
taskStatus = await this.requestPVE(`/nodes/${node}/tasks/${upid}/status`, "GET", { token: true });
|
||||
}
|
||||
if (taskStatus.data.data.exitstatus === "OK") {
|
||||
const result = taskStatus.data.data;
|
||||
const taskLog = await this.requestPVE(`/nodes/${node}/tasks/${upid}/log`, "GET", { token: true });
|
||||
result.log = taskLog.data.data;
|
||||
res.status(200).send(result);
|
||||
res.end();
|
||||
}
|
||||
else {
|
||||
const result = taskStatus.data.data;
|
||||
const taskLog = await this.requestPVE(`/nodes/${node}/tasks/${upid}/log`, "GET", { token: true });
|
||||
result.log = taskLog.data.data;
|
||||
res.status(500).send(result);
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.status(result.status).send(result.data);
|
||||
res.end();
|
||||
console.log(`pve: error ocuured in pve.requestPVE: ${error}`);
|
||||
const result = error.response;
|
||||
result.ok = result.status === 200;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +130,48 @@ export default class PVE extends PVE_BACKEND {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle various proxmox API responses. Handles sync and async responses.
|
||||
* In sync responses, responses are completed when the response arrives. Method returns the response directly.
|
||||
* In async responses, proxmox sends responses with a UPID to track process completion. Method returns the status of the proxmox process once it completes.
|
||||
* @param {string} node response originates from.
|
||||
* @param {Object} result response from proxmox.
|
||||
* @param {Object} res response object of ProxmoxAAS API call.
|
||||
*/
|
||||
async handleResponse (node, result, res) {
|
||||
const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay));
|
||||
if (result.status !== 200) {
|
||||
res.status(result.status).send({ error: result.statusText });
|
||||
res.end();
|
||||
}
|
||||
else if (result.data && typeof (result.data) === "string" && result.data.startsWith("UPID:")) {
|
||||
const upid = result.data;
|
||||
let taskStatus = await this.requestPVE(`/nodes/${node}/tasks/${upid}/status`, "GET", { token: true });
|
||||
while (taskStatus.data.status !== "stopped") {
|
||||
await waitFor(100);
|
||||
taskStatus = await this.requestPVE(`/nodes/${node}/tasks/${upid}/status`, "GET", { token: true });
|
||||
}
|
||||
if (taskStatus.data.exitstatus === "OK") {
|
||||
const result = taskStatus.data;
|
||||
const taskLog = await this.requestPVE(`/nodes/${node}/tasks/${upid}/log`, "GET", { token: true });
|
||||
result.log = taskLog.data;
|
||||
res.status(200).send(result);
|
||||
res.end();
|
||||
}
|
||||
else {
|
||||
const result = taskStatus.data;
|
||||
const taskLog = await this.requestPVE(`/nodes/${node}/tasks/${upid}/log`, "GET", { token: true });
|
||||
result.log = taskLog.data;
|
||||
res.status(500).send(result);
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.status(result.status).send(result.data);
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
|
||||
async getNode (node) {
|
||||
const res = await this.requestFabric(`/nodes/${node}`, "GET");
|
||||
if (res.status !== 200) {
|
||||
@@ -222,25 +230,35 @@ export default class PVE extends PVE_BACKEND {
|
||||
}
|
||||
}
|
||||
|
||||
async getUserResources (user, cookies) {
|
||||
// get user resources with vm filter
|
||||
const res = await this.requestPVE("/cluster/resources?type=vm", "GET", { cookies });
|
||||
async getPoolResources (cookies, pool) {
|
||||
// get pool resources
|
||||
const res = await this.requestPVE(`/pools/?poolid=${pool}`, "GET", { cookies });
|
||||
if (res.status !== 200) {
|
||||
return null;
|
||||
}
|
||||
const data = res.data;
|
||||
if (data.length != 1) {
|
||||
return null;
|
||||
}
|
||||
const poolPVE = data[0];
|
||||
if (poolPVE.poolid != pool) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const userPVEResources = res.data.data;
|
||||
|
||||
const poolPVEResources = poolPVE.members;
|
||||
const resources = {};
|
||||
|
||||
// for each resource, add to the object
|
||||
for (const resource of userPVEResources) {
|
||||
for (const resource of poolPVEResources) {
|
||||
// only add type if it is vm or ct (ie has vmid)
|
||||
if (resource.vmid) {
|
||||
const instance = await this.getInstance(resource.node, resource.vmid);
|
||||
if (instance) {
|
||||
instance.node = resource.node;
|
||||
resources[resource.vmid] = instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
@@ -41,12 +41,3 @@ global.utils.recursiveImportRoutes(app, "/api", "routes");
|
||||
app.get("/api/version", (req, res) => {
|
||||
res.status(200).send({ version: global.package.version });
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - echo request
|
||||
* responses:
|
||||
* - 200: {body: request.body, cookies: request.cookies}
|
||||
*/
|
||||
app.get("/api/echo", (req, res) => {
|
||||
res.status(200).send({ body: req.body, cookies: req.cookies });
|
||||
});
|
||||
|
||||
@@ -64,12 +64,7 @@ router.post("/ticket", async (req, res) => {
|
||||
|
||||
const domain = global.config.application.domain;
|
||||
const userObj = global.utils.getUserObjFromUsername(params.username);
|
||||
let backends = global.userManager.getBackendsByUser(userObj);
|
||||
if (backends == null) {
|
||||
res.status(401).send({ auth: false, error: `${params.username} not found in any ProxmoxAAS backends` });
|
||||
return;
|
||||
}
|
||||
backends = backends.concat(["pve"]);
|
||||
const backends = [global.config.handlers.users, global.config.handlers.instance];
|
||||
const cm = new CookieFetcher();
|
||||
const error = await cm.fetchBackends(backends, userObj, params.password);
|
||||
if (error) {
|
||||
@@ -107,7 +102,7 @@ router.delete("/ticket", async (req, res) => {
|
||||
res.cookie(cookie, "", { domain, path: "/", expires: expire, secure: true, sameSite: "none" });
|
||||
}
|
||||
await global.pve.closeSession(req.cookies);
|
||||
await global.userManager.closeSession(req.cookies);
|
||||
await global.access.closeSession(req.cookies);
|
||||
res.status(200).send({ auth: false });
|
||||
});
|
||||
|
||||
@@ -134,6 +129,6 @@ router.post("/password", async (req, res) => {
|
||||
const newAttributes = {
|
||||
userpassword: params.password
|
||||
};
|
||||
const response = await global.userManager.setUser(userObj, newAttributes, req.cookies);
|
||||
const response = await global.access.setUser(userObj, newAttributes, req.cookies);
|
||||
res.status(response.status).send(response);
|
||||
});
|
||||
|
||||
@@ -3,22 +3,6 @@ export const router = Router({ mergeParams: true });
|
||||
|
||||
const checkAuth = global.utils.checkAuth;
|
||||
|
||||
/**
|
||||
* GET - get all groups
|
||||
* responses:
|
||||
* - 200: {auth: true, groups: Array}
|
||||
* - 401: {auth: false}
|
||||
*/
|
||||
router.get("/", async (req, res) => {
|
||||
// check auth
|
||||
const auth = await checkAuth(req.cookies, res);
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
const groups = await global.userManager.getAllGroups(req.cookies);
|
||||
res.status(200).send({ groups });
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - get specific group
|
||||
* request:
|
||||
@@ -36,6 +20,14 @@ router.get("/:groupname", async (req, res) => {
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
const group = await global.userManager.getGroup(params.groupname, req.cookies);
|
||||
|
||||
const groupObj = global.utils.getGroupObjFromGroupname(params.groupname);
|
||||
const g = await global.access.getGroup(groupObj, req.cookies);
|
||||
if (g.ok !== true) {
|
||||
res.status(g.status).send(g);
|
||||
return;
|
||||
}
|
||||
const group = g.group;
|
||||
|
||||
res.status(200).send({ group });
|
||||
});
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { Router } from "express";
|
||||
export const router = Router({ mergeParams: true });
|
||||
|
||||
const checkAuth = global.utils.checkAuth;
|
||||
const checkUserInPool = global.utils.checkUserInPool;
|
||||
|
||||
/**
|
||||
* GET - get all available cluster pools
|
||||
* returns only pool IDs
|
||||
* responses:
|
||||
* - 200: List of pools
|
||||
* - PVE error
|
||||
*/
|
||||
router.get("/", async (req, res) => {
|
||||
// check auth
|
||||
const auth = await checkAuth(req.cookies, res);
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
||||
|
||||
const pools = {};
|
||||
|
||||
const poolnames = await global.pve.requestPVE("/pools", "GET", { token: true });
|
||||
|
||||
for (const poolpartial of poolnames.data) {
|
||||
const poolname = poolpartial.poolid;
|
||||
|
||||
const p = await global.access.getPool(poolname, req.cookies);
|
||||
if (p.ok !== true) {
|
||||
continue;
|
||||
}
|
||||
const pool = p.pool;
|
||||
|
||||
if (checkUserInPool(pool, userObj)) {
|
||||
const resources = await global.utils.getPoolResources(req, poolname);
|
||||
pool.resources = resources;
|
||||
pools[poolname] = pool;
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).send({ pools });
|
||||
res.end();
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - get specific pool
|
||||
* request:
|
||||
* - poolname: name of pool to get
|
||||
* responses:
|
||||
* - 200: {auth: true, pool: Object}
|
||||
* - 401: {auth: false}
|
||||
*/
|
||||
router.get("/:poolname", async (req, res) => {
|
||||
const params = {
|
||||
poolname: req.params.poolname
|
||||
};
|
||||
// check auth
|
||||
const auth = await checkAuth(req.cookies, res);
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
|
||||
const p = await global.access.getPool(params.poolname, req.cookies);
|
||||
if (p.ok !== true) {
|
||||
res.status(p.status).send(p);
|
||||
return;
|
||||
}
|
||||
const pool = p.pool;
|
||||
const resources = await global.utils.getPoolResources(req, params.poolname);
|
||||
|
||||
pool.resources = resources;
|
||||
|
||||
res.status(200).send({ pool });
|
||||
});
|
||||
@@ -3,22 +3,6 @@ export const router = Router({ mergeParams: true });
|
||||
|
||||
const checkAuth = global.utils.checkAuth;
|
||||
|
||||
/**
|
||||
* GET - get all users
|
||||
* responses:
|
||||
* - 200: {auth:true, users: Array}
|
||||
* - 401: {auth: false}
|
||||
*/
|
||||
router.get("/", async (req, res) => {
|
||||
// check auth
|
||||
const auth = await checkAuth(req.cookies, res);
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
const users = await global.userManager.getAllUsers(req.cookies);
|
||||
res.status(200).send({ users });
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - get specific user
|
||||
* request:
|
||||
@@ -36,7 +20,14 @@ router.get("/:username", async (req, res) => {
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userObj = global.utils.getUserObjFromUsername(params.username);
|
||||
const user = await global.userManager.getUser(userObj, req.cookies);
|
||||
const u = await global.access.getUser(userObj, req.cookies);
|
||||
if (u.ok !== true) {
|
||||
res.status(u.status).send(u);
|
||||
return;
|
||||
}
|
||||
const user = u.user;
|
||||
|
||||
res.status(200).send({ user });
|
||||
});
|
||||
|
||||
+21
-47
@@ -3,7 +3,8 @@ export const router = Router({ mergeParams: true });
|
||||
|
||||
const checkAuth = global.utils.checkAuth;
|
||||
const approveResources = global.utils.approveResources;
|
||||
const getUserResources = global.utils.getUserResources;
|
||||
const getPoolResources = global.utils.getPoolResources;
|
||||
const checkUserInPool = global.utils.checkUserInPool;
|
||||
|
||||
const nodeRegexP = "[\\w-]+";
|
||||
const typeRegexP = "qemu|lxc";
|
||||
@@ -13,33 +14,6 @@ const basePath = `/:node(${nodeRegexP})/:type(${typeRegexP})/:vmid(${vmidRegexP}
|
||||
|
||||
global.utils.recursiveImportRoutes(router, basePath, "cluster", import.meta.url);
|
||||
|
||||
/**
|
||||
* GET - get all available cluster pools
|
||||
* returns only pool IDs
|
||||
* responses:
|
||||
* - 200: List of pools
|
||||
* - PVE error
|
||||
*/
|
||||
router.get("/pools", async (req, res) => {
|
||||
// check auth
|
||||
const auth = await checkAuth(req.cookies, res);
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allPools = await global.pve.requestPVE("/pools", "GET", { token: true });
|
||||
|
||||
if (allPools.status === 200) {
|
||||
const allPoolsIDs = Array.from(allPools.data.data, (x) => x.poolid);
|
||||
res.status(allPools.status).send({ pools: allPoolsIDs });
|
||||
res.end();
|
||||
}
|
||||
else {
|
||||
res.status(allPools.status).send({ error: allPools.statusText });
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - get all available cluster nodes
|
||||
* uses existing user permissions without elevation
|
||||
@@ -58,7 +32,7 @@ router.get("/nodes", async (req, res) => {
|
||||
const allNodes = await global.pve.requestPVE("/nodes", "GET", { cookies: req.cookies });
|
||||
|
||||
if (allNodes.status === 200) {
|
||||
const allNodesIDs = Array.from(allNodes.data.data, (x) => x.node);
|
||||
const allNodesIDs = Array.from(allNodes.data, (x) => x.node);
|
||||
res.status(allNodes.status).send({ nodes: allNodesIDs });
|
||||
res.end();
|
||||
}
|
||||
@@ -89,7 +63,7 @@ router.get(`/:node(${nodeRegexP})/pci`, async (req, res) => {
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
const userNodes = (await global.userManager.getUser(userObj, req.cookies)).cluster.nodes;
|
||||
const userNodes = (await global.access.getPool(userObj, req.cookies)).cluster.nodes;
|
||||
if (userNodes[params.node] !== true) { // user does not have access to the node
|
||||
res.status(401).send({ auth: false, path: params.node });
|
||||
res.end();
|
||||
@@ -97,7 +71,7 @@ router.get(`/:node(${nodeRegexP})/pci`, async (req, res) => {
|
||||
}
|
||||
|
||||
// get remaining user resources
|
||||
const userAvailPci = (await getUserResources(req, userObj)).pci.nodes[params.node]; // we assume that the node list is used. TODO support global lists
|
||||
const userAvailPci = (await getPoolResources(req, userObj)).pci.nodes[params.node]; // we assume that the node list is used. TODO support global lists
|
||||
if (userAvailPci === undefined) { // user has no available devices on this node, so send an empty list
|
||||
res.status(200).send([]);
|
||||
res.end();
|
||||
@@ -201,7 +175,7 @@ router.post(`${basePath}/resources`, async (req, res) => {
|
||||
request.cpu = params.proctype;
|
||||
}
|
||||
// check resource approval
|
||||
const { approved, reason } = await approveResources(req, userObj, request, params.node);
|
||||
const { approved, reason } = await approveResources(req, userObj, params.node, instance.pool, request);
|
||||
if (!approved) {
|
||||
res.status(400).send({ request, error: "Not enough resources to satisfy request.", reason });
|
||||
res.end();
|
||||
@@ -269,11 +243,11 @@ router.post(`${basePath}/create`, async (req, res) => {
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
// get user db config
|
||||
const user = await global.userManager.getUser(userObj, req.cookies);
|
||||
// get pool config
|
||||
const pool = (await global.access.getPool(params.pool, req.cookies)).pool;
|
||||
const vmid = Number.parseInt(params.vmid);
|
||||
const vmidMin = user.cluster.vmid.min;
|
||||
const vmidMax = user.cluster.vmid.max;
|
||||
const vmidMin = pool["vmid-allowed"].min;
|
||||
const vmidMax = pool["vmid-allowed"].max;
|
||||
// check vmid is within allowed range
|
||||
if (vmid < vmidMin || vmid > vmidMax) {
|
||||
res.status(500).send({ error: `Requested vmid ${vmid} is out of allowed range [${vmidMin},${vmidMax}].` });
|
||||
@@ -281,14 +255,14 @@ router.post(`${basePath}/create`, async (req, res) => {
|
||||
return;
|
||||
}
|
||||
// check node is within allowed list
|
||||
if (user.cluster.nodes[params.node] !== true) {
|
||||
res.status(500).send({ error: `Requested node ${params.node} is not in allowed nodes [${user.cluster.nodes}].` });
|
||||
if (pool["nodes-allowed"][params.node] !== true) {
|
||||
res.status(500).send({ error: `Requested node ${params.node} is not in allowed nodes [${pool["nodes-allowed"]}].` });
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
// check if pool is in user allowed pools
|
||||
if (user.cluster.pools[params.pool] !== true) {
|
||||
res.status(500).send({ error: `Requested pool ${params.pool} not in allowed pools [${user.pools}]` });
|
||||
// check if user is in pool
|
||||
if(checkUserInPool(pool, userObj) !== true) {
|
||||
res.status(500).send({ error: `Requested pool ${params.pool} does not contain user ${req.cookies.username}]` });
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
@@ -301,9 +275,9 @@ router.post(`${basePath}/create`, async (req, res) => {
|
||||
request.swap = Number(params.swap) * 1024 ** 2;
|
||||
request[params.rootfslocation] = params.rootfssize * 1024 ** 3;
|
||||
}
|
||||
for (const key of Object.keys(user.templates.instances[params.type])) {
|
||||
const item = user.templates.instances[params.type][key];
|
||||
if (item.resource) {
|
||||
for (const key of Object.keys(pool.templates.instances[params.type])) {
|
||||
const item = pool.templates.instances[params.type][key];
|
||||
if (item.resource.enabled) {
|
||||
if (request[item.resource.name]) {
|
||||
request[item.resource.name] += item.resource.amount;
|
||||
}
|
||||
@@ -313,7 +287,7 @@ router.post(`${basePath}/create`, async (req, res) => {
|
||||
}
|
||||
}
|
||||
// check resource approval
|
||||
const { approved, reason } = await approveResources(req, userObj, request, params.node);
|
||||
const { approved, reason } = await await approveResources(req, userObj, params.node, params.pool, request);
|
||||
if (!approved) {
|
||||
res.status(400).send({ request, error: "Not enough resources to satisfy request.", reason });
|
||||
res.end();
|
||||
@@ -326,8 +300,8 @@ router.post(`${basePath}/create`, async (req, res) => {
|
||||
memory: Number(params.memory),
|
||||
pool: params.pool
|
||||
};
|
||||
for (const key of Object.keys(user.templates.instances[params.type])) {
|
||||
action[key] = user.templates.instances[params.type][key].value;
|
||||
for (const key of Object.keys(pool.templates.instances[params.type])) {
|
||||
action[key] = pool.templates.instances[params.type][key].value;
|
||||
}
|
||||
if (params.type === "lxc") {
|
||||
action.swap = params.swap;
|
||||
|
||||
@@ -33,7 +33,7 @@ router.get("/", async (req, res) => {
|
||||
const storage = global.config.backups.storage;
|
||||
const backups = await global.pve.requestPVE(`/nodes/${params.node}/storage/${storage}/content?content=backup&vmid=${params.vmid}`, "GET", { token: true });
|
||||
if (backups.status === 200) {
|
||||
res.status(backups.status).send(backups.data.data);
|
||||
res.status(backups.status).send(backups.data);
|
||||
}
|
||||
else {
|
||||
res.status(backups.status).send({ error: backups.statusText });
|
||||
@@ -72,9 +72,9 @@ router.post("/", async (req, res) => {
|
||||
// check if number of backups is less than the allowed number
|
||||
const storage = global.config.backups.storage;
|
||||
const backups = await global.pve.requestPVE(`/nodes/${params.node}/storage/${storage}/content?content=backup&vmid=${params.vmid}`, "GET", { token: true });
|
||||
const numBackups = backups.data.data.length;
|
||||
const numBackups = backups.data.length;
|
||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
||||
const maxAllowed = (await global.userManager.getUser(userObj, req.cookies)).cluster.backups.max;
|
||||
const maxAllowed = (await global.access.getUser(userObj, req.cookies)).cluster.backups.max;
|
||||
if (backups.status !== 200) {
|
||||
res.status(backups.status).send({ error: backups.statusText });
|
||||
return;
|
||||
@@ -94,7 +94,7 @@ router.post("/", async (req, res) => {
|
||||
"notes-template": params.notes
|
||||
};
|
||||
const result = await global.pve.requestPVE(`/nodes/${params.node}/vzdump`, "POST", { token: true }, body);
|
||||
res.status(result.status).send(result.data.data);
|
||||
res.status(result.status).send(result.data);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -136,7 +136,7 @@ router.post("/notes", async (req, res) => {
|
||||
return;
|
||||
}
|
||||
let found = false;
|
||||
for (const volume of backups.data.data) {
|
||||
for (const volume of backups.data) {
|
||||
if (volume.subtype === params.type && String(volume.vmid) === params.vmid && volume.content === "backup" && volume.volid === params.volid) {
|
||||
found = true;
|
||||
}
|
||||
@@ -196,7 +196,7 @@ router.delete("/", async (req, res) => {
|
||||
return;
|
||||
}
|
||||
let found = false;
|
||||
for (const volume of backups.data.data) {
|
||||
for (const volume of backups.data) {
|
||||
if (volume.subtype === params.type && String(volume.vmid) === params.vmid && volume.content === "backup" && volume.volid === params.volid) {
|
||||
found = true;
|
||||
}
|
||||
@@ -208,7 +208,7 @@ router.delete("/", async (req, res) => {
|
||||
|
||||
// found a valid backup with matching vmid and volid
|
||||
const result = await global.pve.requestPVE(`/nodes/${params.node}/storage/${storage}/content/${params.volid}?delay=5`, "DELETE", { token: true });
|
||||
res.status(result.status).send(result.data.data);
|
||||
res.status(result.status).send(result.data);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -248,7 +248,7 @@ router.post("/restore", async (req, res) => {
|
||||
return;
|
||||
}
|
||||
let found = false;
|
||||
for (const volume of backups.data.data) {
|
||||
for (const volume of backups.data) {
|
||||
if (volume.subtype === params.type && String(volume.vmid) === params.vmid && volume.content === "backup" && volume.volid === params.volid) {
|
||||
found = true;
|
||||
}
|
||||
@@ -281,7 +281,6 @@ router.post("/restore", async (req, res) => {
|
||||
}
|
||||
|
||||
const result = await global.pve.requestPVE(`/nodes/${params.node}/${params.type}/`, "POST", { token: true }, body);
|
||||
console.log(result);
|
||||
if (result.status === 200) {
|
||||
res.status(result.status).send();
|
||||
}
|
||||
|
||||
@@ -145,6 +145,8 @@ router.post("/:disk/resize", async (req, res) => {
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
// get instance config for pool membership
|
||||
const instance = await global.pve.getInstance(params.node, params.vmid);
|
||||
// check disk existence
|
||||
const disk = await global.pve.getDisk(params.node, params.vmid, params.disk); // get target disk
|
||||
if (!disk) { // exit if disk does not exist
|
||||
@@ -157,7 +159,7 @@ router.post("/:disk/resize", async (req, res) => {
|
||||
const request = {};
|
||||
request[storage] = Number(params.size * 1024 ** 3); // setup request object
|
||||
// check request approval
|
||||
const { approved } = await approveResources(req, userObj, request, params.node);
|
||||
const { approved } = await approveResources(req, userObj, params.node, instance.pool, request);
|
||||
if (!approved) {
|
||||
res.status(500).send({ request, error: `Storage ${storage} could not fulfill request of size ${params.size}G.` });
|
||||
res.end();
|
||||
@@ -205,6 +207,8 @@ router.post("/:disk/move", async (req, res) => {
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
// get instance config for pool membership
|
||||
const instance = await global.pve.getInstance(params.node, params.vmid);
|
||||
// check disk existence
|
||||
const disk = await global.pve.getDisk(params.node, params.vmid, params.disk); // get target disk
|
||||
if (!disk) { // exit if disk does not exist
|
||||
@@ -220,7 +224,7 @@ router.post("/:disk/move", async (req, res) => {
|
||||
request[dstStorage] = Number(size); // always decrease destination storage by size
|
||||
}
|
||||
// check request approval
|
||||
const { approved } = await approveResources(req, userObj, request, params.node);
|
||||
const { approved } = await approveResources(req, userObj, params.node, instance.pool, request);
|
||||
if (!approved) {
|
||||
res.status(500).send({ request, error: `Storage ${params.storage} could not fulfill request of size ${params.size}G.` });
|
||||
res.end();
|
||||
@@ -324,6 +328,8 @@ router.post("/:disk/create", async (req, res) => {
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
// get instance config for pool membership
|
||||
const instance = await global.pve.getInstance(params.node, params.vmid);
|
||||
// disk must not exist
|
||||
const disk = await global.pve.getDisk(params.node, params.vmid, params.disk);
|
||||
if (disk) {
|
||||
@@ -337,7 +343,7 @@ router.post("/:disk/create", async (req, res) => {
|
||||
// setup request
|
||||
request[params.storage] = Number(params.size * 1024 ** 3);
|
||||
// check request approval
|
||||
const { approved } = await approveResources(req, userObj, request, params.node);
|
||||
const { approved } = await approveResources(req, userObj, params.node, instance.pool, request);
|
||||
if (!approved) {
|
||||
res.status(500).send({ request, error: `Storage ${params.storage} could not fulfill request of size ${params.size}G.` });
|
||||
res.end();
|
||||
|
||||
@@ -36,6 +36,8 @@ router.post("/:netid/create", async (req, res) => {
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
// get instance config for pool membership
|
||||
const instance = await global.pve.getInstance(params.node, params.vmid);
|
||||
// net interface must not exist
|
||||
const net = await global.pve.getNet(params.node, params.vmid, params.netid);
|
||||
if (net) {
|
||||
@@ -53,14 +55,14 @@ router.post("/:netid/create", async (req, res) => {
|
||||
};
|
||||
// check resource approval
|
||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
||||
const { approved } = await approveResources(req, userObj, request, params.node);
|
||||
const { approved } = await approveResources(req, userObj, params.node, instance.pool, request);
|
||||
if (!approved) {
|
||||
res.status(500).send({ request, error: `Could not fulfil network request of ${params.rate}MB/s.` });
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
// setup action
|
||||
const nc = (await global.userManager.getUser(userObj, req.cookies)).templates.network[params.type];
|
||||
const nc = (await global.access.getUser(userObj, req.cookies)).templates.network[params.type];
|
||||
const action = {};
|
||||
if (params.type === "lxc") {
|
||||
action[`${params.netid}`] = `name=${params.name},bridge=${nc.bridge},ip=${nc.ip},ip6=${nc.ip6},tag=${nc.vlan},type=${nc.type},rate=${params.rate}`;
|
||||
@@ -105,6 +107,8 @@ router.post("/:netid/modify", async (req, res) => {
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
// get instance config for pool membership
|
||||
const instance = await global.pve.getInstance(params.node, params.vmid);
|
||||
// net interface must already exist
|
||||
const net = await global.pve.getNet(params.node, params.vmid, params.netid);
|
||||
if (!net) {
|
||||
@@ -117,7 +121,7 @@ router.post("/:netid/modify", async (req, res) => {
|
||||
};
|
||||
// check resource approval
|
||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
||||
const { approved } = await approveResources(req, userObj, request, params.node);
|
||||
const { approved } = await approveResources(req, userObj, params.node, instance.pool, request);
|
||||
if (!approved) {
|
||||
res.status(500).send({ request, error: `Could not fulfil network request of ${params.rate}MB/s.` });
|
||||
res.end();
|
||||
|
||||
@@ -78,6 +78,8 @@ router.post("/:hostpci/modify", async (req, res) => {
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
// get instance config for pool membership
|
||||
const instance = await global.pve.getInstance(params.node, params.vmid);
|
||||
// force all functions
|
||||
params.device = params.device.split(".")[0];
|
||||
// device must exist to be modified
|
||||
@@ -100,7 +102,7 @@ router.post("/:hostpci/modify", async (req, res) => {
|
||||
return;
|
||||
}
|
||||
// check resource approval
|
||||
const { approved } = await approveResources(req, userObj, request, params.node);
|
||||
const { approved } = await approveResources(req, userObj, params.node, instance.pool, request);
|
||||
if (!approved) {
|
||||
res.status(500).send({ request, error: `Could not fulfil request for ${requestedDevice.device_name}.` });
|
||||
res.end();
|
||||
@@ -158,6 +160,8 @@ router.post("/:hostpci/create", async (req, res) => {
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
// get instance config for pool membership
|
||||
const instance = await global.pve.getInstance(params.node, params.vmid);
|
||||
// force all functions
|
||||
params.device = params.device.split(".")[0];
|
||||
// device must not exist to be added
|
||||
@@ -173,7 +177,7 @@ router.post("/:hostpci/create", async (req, res) => {
|
||||
const request = { pci: requestedDevice.device_name };
|
||||
// check resource approval
|
||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
||||
const { approved } = await approveResources(req, userObj, request, params.node);
|
||||
const { approved } = await approveResources(req, userObj, params.node, instance.pool, request);
|
||||
if (!approved) {
|
||||
res.status(500).send({ request, error: `Could not fulfil request for ${requestedDevice.device_name}.` });
|
||||
res.end();
|
||||
|
||||
+5
-6
@@ -52,7 +52,7 @@ if (schemes.hash.enabled) {
|
||||
return;
|
||||
}
|
||||
// get current cluster resources - do not use fabric here because fabric is not always updated to changes like up/down state changes
|
||||
const status = (await global.pve.requestPVE("/cluster/resources", "GET", { cookies: req.cookies })).data.data;
|
||||
const status = (await global.pve.requestPVE("/cluster/resources", "GET", { cookies: req.cookies })).data;
|
||||
// filter out just state information of resources that are needed
|
||||
const state = extractClusterState(status, resourceTypes);
|
||||
res.status(200).send(getObjectHash(state));
|
||||
@@ -166,11 +166,10 @@ if (schemes.interrupt.enabled) {
|
||||
}
|
||||
else {
|
||||
wsServer.handleUpgrade(req, socket, head, async (socket) => {
|
||||
// get the user pools
|
||||
const userObj = global.utils.getUserObjFromUsername(cookies.username);
|
||||
const pools = Object.keys((await global.userManager.getUser(userObj, cookies)).cluster.pools);
|
||||
// use user cookies to determine which pools they can see, lazily assume that if a user can audit a pool they are also can audit pool member state
|
||||
const pools = await global.pve.requestPVE("/pools", "GET", { cookies });
|
||||
// emit the connection to initialize socket
|
||||
wsServer.emit("connection", socket, cookies.username, pools);
|
||||
wsServer.emit("connection", socket, cookies.username, Object.keys(pools.data));
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -190,7 +189,7 @@ if (schemes.interrupt.enabled) {
|
||||
return;
|
||||
}
|
||||
// get current cluster resources
|
||||
const status = (await global.pve.requestPVE("/cluster/resources", "GET", { token: true })).data.data;
|
||||
const status = (await global.pve.requestPVE("/cluster/resources", "GET", { token: true })).data;
|
||||
// filter out just state information of resources that are needed, and hash each one
|
||||
const currState = extractClusterState(status, resourceTypes, true);
|
||||
// get a map of users to send sync notifications
|
||||
|
||||
+2
-56
@@ -4,60 +4,6 @@ export const router = Router({ mergeParams: true }); ;
|
||||
const config = global.config;
|
||||
const checkAuth = global.utils.checkAuth;
|
||||
|
||||
/**
|
||||
* GET - get db user resource information including allocated, free, and maximum resource values along with resource metadata
|
||||
* responses:
|
||||
* - 200: {avail: Object, max: Object, used: Object, resources: Object}
|
||||
* - 401: {auth: false}
|
||||
*/
|
||||
router.get("/dynamic/resources", async (req, res) => {
|
||||
const params = {
|
||||
username: req.cookies.username
|
||||
};
|
||||
|
||||
// check auth
|
||||
const auth = await checkAuth(req.cookies, res);
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userObj = global.utils.getUserObjFromUsername(params.username);
|
||||
|
||||
const resources = await global.utils.getUserResources(req, userObj);
|
||||
res.status(200).send(resources);
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - get db user configuration by key
|
||||
* request:
|
||||
* - key: string - user config key
|
||||
* responses:
|
||||
* - 200: Object
|
||||
* - 401: {auth: false}
|
||||
* - 401: {auth: false, error: string}
|
||||
*/
|
||||
router.get("/config/:key", async (req, res) => {
|
||||
const params = {
|
||||
key: req.params.key
|
||||
};
|
||||
|
||||
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
|
||||
|
||||
// check auth
|
||||
const auth = await checkAuth(req.cookies, res);
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
const allowKeys = ["resources", "cluster"];
|
||||
if (allowKeys.includes(params.key)) {
|
||||
const config = await global.userManager.getUser(userObj, req.cookies);
|
||||
res.status(200).send(config[params.key]);
|
||||
}
|
||||
else {
|
||||
res.status(401).send({ auth: false, error: `User is not authorized to access /user/config/${params.key}.` });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET - get user accessible iso files
|
||||
* response:
|
||||
@@ -78,7 +24,7 @@ router.get("/vm-isos", async (req, res) => {
|
||||
res.status(content.status).send({ error: content.statusText });
|
||||
return;
|
||||
}
|
||||
const isos = content.data.data;
|
||||
const isos = content.data;
|
||||
const userIsos = [];
|
||||
isos.forEach((iso) => {
|
||||
iso.name = iso.volid.replace(`${userIsoConfig.storage}:iso/`, "");
|
||||
@@ -108,7 +54,7 @@ router.get("/ct-templates", async (req, res) => {
|
||||
res.status(content.status).send({ error: content.statusText });
|
||||
return;
|
||||
}
|
||||
const isos = content.data.data;
|
||||
const isos = content.data;
|
||||
const userIsos = [];
|
||||
isos.forEach((iso) => {
|
||||
iso.name = iso.volid.replace(`${userIsoConfig.storage}:vztmpl/`, "");
|
||||
|
||||
+122
-76
@@ -36,7 +36,7 @@ export async function checkAuth (cookies, res, vmpath = null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((await global.userManager.getUser(userObj, cookies)) === null) { // check if user exists in database
|
||||
if ((await global.access.getUser(userObj, cookies)) === null) { // check if user exists in database
|
||||
res.status(401).send({ auth, path: vmpath ? `${vmpath}/config` : "/version", error: `User ${cookies.username} not found in database.` });
|
||||
res.end();
|
||||
return false;
|
||||
@@ -60,43 +60,44 @@ export async function checkAuth (cookies, res, vmpath = null) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user resource data including used, available, and maximum resources.
|
||||
* Get pool resource data including used, available, and maximum resources.
|
||||
* @param {Object} req ProxmoxAAS API request object.
|
||||
* @param {{id: string, realm: string}} user object of user to get resource data.
|
||||
* @returns {{used: Object, avail: Object, max: Object, resources: Object}} used, available, maximum, and resource metadata for the specified user.
|
||||
*/
|
||||
export async function getUserResources (req, user) {
|
||||
const dbResources = global.config.resources;
|
||||
const userResources = (await global.userManager.getUser(user, req.cookies)).resources;
|
||||
export async function getPoolResources (req, pool) {
|
||||
const configResources = global.config.resources;
|
||||
const poolConfig = await global.access.getPool(pool, req.cookies);
|
||||
const poolResources = poolConfig.pool.resources;
|
||||
|
||||
// setup the user resource object with used and avail for each resource and each resource pool
|
||||
// setup the pool resource object with used and avail for each resource and each resource pool
|
||||
// also add a total counter for each resource (only used for display, not used to check requests)
|
||||
for (const resourceName of Object.keys(userResources)) {
|
||||
if (dbResources[resourceName].type === "list") {
|
||||
userResources[resourceName].total = [];
|
||||
userResources[resourceName].global.forEach((e) => {
|
||||
for (const resourceName of Object.keys(poolResources)) {
|
||||
if (configResources[resourceName].type === "list") {
|
||||
poolResources[resourceName].total = [];
|
||||
poolResources[resourceName].global.forEach((e) => {
|
||||
e.used = 0;
|
||||
e.avail = e.max;
|
||||
const index = userResources[resourceName].total.findIndex((availEelement) => e.match === availEelement.match);
|
||||
const index = poolResources[resourceName].total.findIndex((availEelement) => e.match === availEelement.match);
|
||||
if (index === -1) {
|
||||
userResources[resourceName].total.push(structuredClone(e));
|
||||
poolResources[resourceName].total.push(structuredClone(e));
|
||||
}
|
||||
else {
|
||||
userResources[resourceName].total[index].max += e.max;
|
||||
userResources[resourceName].total[index].avail += e.avail;
|
||||
poolResources[resourceName].total[index].max += e.max;
|
||||
poolResources[resourceName].total[index].avail += e.avail;
|
||||
}
|
||||
});
|
||||
for (const nodeName of Object.keys(userResources[resourceName].nodes)) {
|
||||
userResources[resourceName].nodes[nodeName].forEach((e) => {
|
||||
for (const nodeName of Object.keys(poolResources[resourceName].nodes)) {
|
||||
poolResources[resourceName].nodes[nodeName].forEach((e) => {
|
||||
e.used = 0;
|
||||
e.avail = e.max;
|
||||
const index = userResources[resourceName].total.findIndex((availEelement) => e.match === availEelement.match);
|
||||
const index = poolResources[resourceName].total.findIndex((availEelement) => e.match === availEelement.match);
|
||||
if (index === -1) {
|
||||
userResources[resourceName].total.push(structuredClone(e));
|
||||
poolResources[resourceName].total.push(structuredClone(e));
|
||||
}
|
||||
else {
|
||||
userResources[resourceName].total[index].max += e.max;
|
||||
userResources[resourceName].total[index].avail += e.avail;
|
||||
poolResources[resourceName].total[index].max += e.max;
|
||||
poolResources[resourceName].total[index].avail += e.avail;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -107,21 +108,21 @@ export async function getUserResources (req, user) {
|
||||
used: 0,
|
||||
avail: 0
|
||||
};
|
||||
userResources[resourceName].global.used = 0;
|
||||
userResources[resourceName].global.avail = userResources[resourceName].global.max;
|
||||
total.max += userResources[resourceName].global.max;
|
||||
total.avail += userResources[resourceName].global.avail;
|
||||
for (const nodeName of Object.keys(userResources[resourceName].nodes)) {
|
||||
userResources[resourceName].nodes[nodeName].used = 0;
|
||||
userResources[resourceName].nodes[nodeName].avail = userResources[resourceName].nodes[nodeName].max;
|
||||
total.max += userResources[resourceName].nodes[nodeName].max;
|
||||
total.avail += userResources[resourceName].nodes[nodeName].avail;
|
||||
poolResources[resourceName].global.used = 0;
|
||||
poolResources[resourceName].global.avail = poolResources[resourceName].global.max;
|
||||
total.max += poolResources[resourceName].global.max;
|
||||
total.avail += poolResources[resourceName].global.avail;
|
||||
for (const nodeName of Object.keys(poolResources[resourceName].nodes)) {
|
||||
poolResources[resourceName].nodes[nodeName].used = 0;
|
||||
poolResources[resourceName].nodes[nodeName].avail = poolResources[resourceName].nodes[nodeName].max;
|
||||
total.max += poolResources[resourceName].nodes[nodeName].max;
|
||||
total.avail += poolResources[resourceName].nodes[nodeName].avail;
|
||||
}
|
||||
userResources[resourceName].total = total;
|
||||
poolResources[resourceName].total = total;
|
||||
}
|
||||
}
|
||||
|
||||
const configs = await global.pve.getUserResources(user, req.cookies);
|
||||
const configs = await global.pve.getPoolResources(req.cookies, pool);
|
||||
|
||||
for (const vmid in configs) {
|
||||
const config = configs[vmid];
|
||||
@@ -129,20 +130,20 @@ export async function getUserResources (req, user) {
|
||||
// count basic numeric resources
|
||||
for (const resourceName of Object.keys(config)) {
|
||||
// numeric resource type
|
||||
if (resourceName in dbResources && dbResources[resourceName].type === "numeric") {
|
||||
if (resourceName in configResources && configResources[resourceName].type === "numeric") {
|
||||
const val = Number(config[resourceName]);
|
||||
// if the instance's node is restricted by this resource, add it to the instance's used value
|
||||
if (nodeName in userResources[resourceName].nodes) {
|
||||
userResources[resourceName].nodes[nodeName].used += val;
|
||||
userResources[resourceName].nodes[nodeName].avail -= val;
|
||||
if (nodeName in poolResources[resourceName].nodes) {
|
||||
poolResources[resourceName].nodes[nodeName].used += val;
|
||||
poolResources[resourceName].nodes[nodeName].avail -= val;
|
||||
}
|
||||
// otherwise add the resource to the global pool
|
||||
else {
|
||||
userResources[resourceName].global.used += val;
|
||||
userResources[resourceName].global.avail -= val;
|
||||
poolResources[resourceName].global.used += val;
|
||||
poolResources[resourceName].global.avail -= val;
|
||||
}
|
||||
userResources[resourceName].total.used += val;
|
||||
userResources[resourceName].total.avail -= val;
|
||||
poolResources[resourceName].total.used += val;
|
||||
poolResources[resourceName].total.avail -= val;
|
||||
}
|
||||
}
|
||||
// count disk resources in volumes
|
||||
@@ -151,38 +152,38 @@ export async function getUserResources (req, user) {
|
||||
const storage = disk.storage;
|
||||
const size = disk.size;
|
||||
// only process disk if its storage is in the user resources to be counted
|
||||
if (storage in userResources) {
|
||||
if (storage in poolResources) {
|
||||
// if the instance's node is restricted by this resource, add it to the instance's used value
|
||||
if (nodeName in userResources[storage].nodes) {
|
||||
userResources[storage].nodes[nodeName].used += size;
|
||||
userResources[storage].nodes[nodeName].avail -= size;
|
||||
if (nodeName in poolResources[storage].nodes) {
|
||||
poolResources[storage].nodes[nodeName].used += size;
|
||||
poolResources[storage].nodes[nodeName].avail -= size;
|
||||
}
|
||||
// otherwise add the resource to the global pool
|
||||
else {
|
||||
userResources[storage].global.used += size;
|
||||
userResources[storage].global.avail -= size;
|
||||
poolResources[storage].global.used += size;
|
||||
poolResources[storage].global.avail -= size;
|
||||
}
|
||||
userResources[storage].total.used += size;
|
||||
userResources[storage].total.avail -= size;
|
||||
poolResources[storage].total.used += size;
|
||||
poolResources[storage].total.avail -= size;
|
||||
}
|
||||
}
|
||||
// count net resources in nets
|
||||
for (const netid in config.nets) {
|
||||
const net = config.nets[netid];
|
||||
const rate = net.rate;
|
||||
if (userResources.network) {
|
||||
if (poolResources.network) {
|
||||
// if the instance's node is restricted by this resource, add it to the instance's used value
|
||||
if (nodeName in userResources.network.nodes) {
|
||||
userResources.network.nodes[nodeName].used += rate;
|
||||
userResources.network.nodes[nodeName].avail -= rate;
|
||||
if (nodeName in poolResources.network.nodes) {
|
||||
poolResources.network.nodes[nodeName].used += rate;
|
||||
poolResources.network.nodes[nodeName].avail -= rate;
|
||||
}
|
||||
// otherwise add the resource to the global pool
|
||||
else {
|
||||
userResources.network.global.used += rate;
|
||||
userResources.network.global.avail -= rate;
|
||||
poolResources.network.global.used += rate;
|
||||
poolResources.network.global.avail -= rate;
|
||||
}
|
||||
userResources.network.total.used += rate;
|
||||
userResources.network.total.avail -= rate;
|
||||
poolResources.network.total.used += rate;
|
||||
poolResources.network.total.avail -= rate;
|
||||
}
|
||||
}
|
||||
// count pci device resources in devices
|
||||
@@ -190,49 +191,51 @@ export async function getUserResources (req, user) {
|
||||
const device = config.devices[deviceid];
|
||||
const name = device.device_name;
|
||||
// if the node has a node specific rule, add it there
|
||||
if (nodeName in userResources.pci.nodes) {
|
||||
const index = userResources.pci.nodes[nodeName].findIndex((availEelement) => name.includes(availEelement.match));
|
||||
if (nodeName in poolResources.pci.nodes) {
|
||||
const index = poolResources.pci.nodes[nodeName].findIndex((availEelement) => name.includes(availEelement.match));
|
||||
if (index >= 0) {
|
||||
userResources.pci.nodes[nodeName][index].used++;
|
||||
userResources.pci.nodes[nodeName][index].avail--;
|
||||
poolResources.pci.nodes[nodeName][index].used++;
|
||||
poolResources.pci.nodes[nodeName][index].avail--;
|
||||
}
|
||||
}
|
||||
// otherwise try to add the resource to the global pool
|
||||
else {
|
||||
const index = userResources.pci.global.findIndex((availEelement) => name.includes(availEelement.match));
|
||||
const index = poolResources.pci.global.findIndex((availEelement) => name.includes(availEelement.match));
|
||||
if (index >= 0) { // device resource is in the user's global list then increment it by 1
|
||||
userResources.pci.global[index].used++;
|
||||
userResources.pci.global[index].avail--;
|
||||
poolResources.pci.global[index].used++;
|
||||
poolResources.pci.global[index].avail--;
|
||||
}
|
||||
}
|
||||
// finally, add the device to the total map
|
||||
const index = userResources.pci.total.findIndex((availEelement) => name.includes(availEelement.match));
|
||||
const index = poolResources.pci.total.findIndex((availEelement) => name.includes(availEelement.match));
|
||||
if (index >= 0) {
|
||||
userResources.pci.total[index].used++;
|
||||
userResources.pci.total[index].avail--;
|
||||
poolResources.pci.total[index].used++;
|
||||
poolResources.pci.total[index].avail--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return userResources;
|
||||
return poolResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check approval for user requesting additional resources. Generally, subtracts the request from available resources and ensures request can be fulfilled by the available resources.
|
||||
* @param {Object} req ProxmoxAAS API request object.
|
||||
* @param {{id: string, realm: string}} user object of user requesting additional resources.
|
||||
* @param {string} node name of node hosting requested resource(s)
|
||||
* @param {string} pool name of pool hosting requested resource(s)
|
||||
* @param {Object} request k-v pairs of resources and requested amounts
|
||||
* @returns {boolean, Object} true if the available resources can fullfill the requested resources, false otherwise.
|
||||
*/
|
||||
export async function approveResources (req, user, request, node) {
|
||||
const dbResources = global.config.resources;
|
||||
const userResources = await getUserResources(req, user);
|
||||
export async function approveResources (req, user, node, pool, request) {
|
||||
const configResources = global.config.resources;
|
||||
const poolResources = await getPoolResources(req, pool);
|
||||
// let approved = true;
|
||||
const reason = {};
|
||||
|
||||
for (const key in request) {
|
||||
// if requested resource is not specified in user resources, assume it's not allowed
|
||||
if (!(key in userResources)) {
|
||||
if (!(key in poolResources)) {
|
||||
// approved = false;
|
||||
reason[key] = { approved: false, reason: `${key} not allowed` };
|
||||
continue;
|
||||
@@ -240,17 +243,17 @@ export async function approveResources (req, user, request, node) {
|
||||
}
|
||||
|
||||
// use node specific quota if there is one available, otherwise use the global resource quota
|
||||
const inNode = node in userResources[key].nodes;
|
||||
const resourceData = inNode ? userResources[key].nodes[node] : userResources[key].global;
|
||||
const inNode = node in poolResources[key].nodes;
|
||||
const resourceData = inNode ? poolResources[key].nodes[node] : poolResources[key].global;
|
||||
|
||||
// if the resource type is list, check if the requested resource exists in the list
|
||||
if (dbResources[key].type === "list") {
|
||||
if (configResources[key].type === "list") {
|
||||
const index = resourceData.findIndex((availElement) => request[key].includes(availElement.match));
|
||||
// if no matching resource when index == -1, then remaining is -1 otherwise use the remaining value
|
||||
const avail = index === -1 ? false : resourceData[index].avail > 0;
|
||||
if (avail !== dbResources[key].whitelist) {
|
||||
if (avail !== configResources[key].whitelist) {
|
||||
// approved = false;
|
||||
reason[key] = { approved: false, reason: `${key} ${dbResources[key].whitelist ? "not in whitelist" : "in blacklist"}` };
|
||||
reason[key] = { approved: false, reason: `${key} ${configResources[key].whitelist ? "not in whitelist" : "in blacklist"}` };
|
||||
// return;
|
||||
continue;
|
||||
}
|
||||
@@ -339,7 +342,7 @@ export function readJSONFile (path) {
|
||||
/**
|
||||
*
|
||||
* @param {*} username
|
||||
* @returns {Object | null} user object containing username and realm or null if user does not exist
|
||||
* @returns {Object | null} user object containing userid and realm or null if username format was invalid
|
||||
*/
|
||||
export function getUserObjFromUsername (username) {
|
||||
if (username) {
|
||||
@@ -352,3 +355,46 @@ export function getUserObjFromUsername (username) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} groupname
|
||||
* @returns {Object | null} user object containing groupid and realm or null if groupname format was invalid
|
||||
*/
|
||||
export function getGroupObjFromGroupname (groupname) {
|
||||
if (groupname) {
|
||||
if (groupname.includes("-")) {
|
||||
const groupRealm = groupname.split("-").at(-1);
|
||||
const groupID = groupname.replace(`-${groupRealm}`, "");
|
||||
const groupObj = { id: groupID, realm: groupRealm };
|
||||
return groupObj;
|
||||
}
|
||||
else {
|
||||
const groupRealm = "pve";
|
||||
const groupID = groupname;
|
||||
const groupObj = { id: groupID, realm: groupRealm };
|
||||
return groupObj;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} poolObj pool data object
|
||||
* @param {Object} userObj user object containing id and realm
|
||||
* @returns {boolean} true if userObj in poolObj
|
||||
*/
|
||||
export function checkUserInPool(poolObj, userObj) {
|
||||
for (const group of poolObj.groups) {
|
||||
// assumption: pool listed groups are all relevant memberships (ie are paas client role)
|
||||
for (const user of group.users) {
|
||||
if (user.username.uid === userObj.id && user.username.realm === userObj.realm) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user