improve backend handler design

This commit is contained in:
Arthur Lu 2024-10-09 04:36:35 +00:00
parent 072b5ef2d4
commit 4984877ab7
3 changed files with 139 additions and 124 deletions

View File

@ -54,17 +54,39 @@ class BACKEND {
} }
} }
export class AtomicChange {
constructor (valid, delta, callback, status = { ok: true, status: 200, message: "" }) {
this.valid = valid;
this.delta = delta;
this.callback = callback;
this.status = status;
}
/**
* Execute the change using the saved delta using the callback function
*/
async commit () {
const res = await this.callback(this.delta);
return res;
}
}
export function doNothingCallback (delta) {
return { ok: true, status: 200, message: "" };
}
/** /**
* Interface for backend types that store/interact with user & group data. * Interface for backend types that store/interact with user & group data.
* Not all backends need to implement all interface methods. * Not all backends need to implement all interface methods.
*/ */
class USER_BACKEND extends BACKEND { class USER_BACKEND extends BACKEND {
/** /**
* Add user to 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.
* @param {{id: string, realm: string}} user * @param {{id: string, realm: string}} user
* @param {Object} attributes user attributes * @param {Object} attributes user attributes
* @param {Object} params authentication params, usually req.cookies * @param {Object} params authentication params, usually req.cookies
* @returns {{ok: boolean, status: number, message: string}} error object or null * @returns {AtomicChange} atomic change object
*/ */
addUser (user, attributes, params) {} addUser (user, attributes, params) {}
@ -84,28 +106,31 @@ class USER_BACKEND extends BACKEND {
getAllUsers (params) {} getAllUsers (params) {}
/** /**
* Modify user in backend * Validate a set user 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, realm: string}} user
* @param {Object} attributes new user attributes to modify * @param {Object} attributes new user attributes to modify
* @param {Object} params authentication params, usually req.cookies * @param {Object} params authentication params, usually req.cookies
* @returns {{ok: boolean, status: number, message: string}} error object or null * @returns {AtomicChange} atomic change object
*/ */
setUser (user, attributes, params) {} setUser (user, attributes, params) {}
/** /**
* Delete user from backend * Validate a delete user 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, realm: string}} user
* @param {Object} params authentication params, usually req.cookies * @param {Object} params authentication params, usually req.cookies
* @returns {{ok: boolean, status: number, message: string}} error object or null * @returns {AtomicChange} atomic change object
*/ */
delUser (user, params) {} delUser (user, params) {}
/** /**
* Add group to backend * Validate an add group operation with the following parameters.
* @param {{id: string}} group * Returns whether the change is valid and a delta object to be used in the operation.
* @param {{id: string, realm: string}} group
* @param {Object} attributes group attributes * @param {Object} attributes group attributes
* @param {Object} params authentication params, usually req.cookies * @param {Object} params authentication params, usually req.cookies
* @returns {{ok: boolean, status: number, message: string}} error object or null * @returns {AtomicChange} atomic change object
*/ */
addGroup (group, attributes, params) {} addGroup (group, attributes, params) {}
@ -125,37 +150,40 @@ class USER_BACKEND extends BACKEND {
getAllGroups (params) {} getAllGroups (params) {}
/** /**
* Modify group in backend * Validate a set group operation with the following parameters.
* @param {{id: string}} group * Returns whether the change is valid and a delta object to be used in the operation.
* @param {Object} attributes new group attributes to modify * @param {{id: string, realm: string}} group
* @param {Object} attributes group attributes
* @param {Object} params authentication params, usually req.cookies * @param {Object} params authentication params, usually req.cookies
* @returns {{ok: boolean, status: number, message: string}} error object or null * @returns {AtomicChange} atomic change object
*/ */
setGroup (group, attributes, params) {} setGroup (group, attributes, params) {}
/** /**
* Delete group from backend * Validate a del group operation with the following parameters.
* @param {{id: string}} group * Returns whether the change is valid and a delta object to be used in the operation.
* @param {{id: string, realm: string}} group
* @param {Object} params authentication params, usually req.cookies * @param {Object} params authentication params, usually req.cookies
* @returns {{ok: boolean, status: number, message: string}} error object or null * @returns {AtomicChange} atomic change object
*/ */
delGroup (group, params) {} delGroup (group, attributes, params) {}
/** /**
* Add user to group * 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, realm: string}} user
* @param {{id: string}} group * @param {{id: string}} group
* @param {Object} params authentication params, usually req.cookies * @param {Object} params authentication params, usually req.cookies
* @returns {{ok: boolean, status: number, message: string}} error object or null * @returns {AtomicChange} atomic change object
*/ */
addUserToGroup (user, group, params) {} addUserToGroup (user, group, params) {}
/** /**
* Remove user from group * 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, realm: string}} user
* @param {{id: string}} group * @param {{id: string}} group
* @param {Object} params authentication params, usually req.cookies * @param {Object} params authentication params, usually req.cookies
* @returns {{ok: boolean, status: number, message: string}} error object or null * @returns {AtomicChange} atomic change object
*/ */
delUserFromGroup (user, group, params) {} delUserFromGroup (user, group, params) {}
} }
@ -218,20 +246,30 @@ class USER_BACKEND_MANAGER extends USER_BACKEND {
} }
async setUser (user, attributes, params) { async setUser (user, attributes, params) {
const results = { 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.stauts;
}
atomicChanges.push(atomicChange); // queue callback into array
}
const response = {
ok: true, ok: true,
status: 200, status: 200,
message: "" message: "",
allResponses: []
}; };
for (const backend of this.#config.realm[user.realm]) { for (const atomicChange of atomicChanges) {
const result = await global.backends[backend].setUser(user, attributes, params); const atomicResponse = await atomicChange.commit();
if (!result) { if (atomicResponse.ok === false) {
results.ok = false; response.ok = false;
results.status = 500; response.status = atomicResponse.status;
return results; response.message = atomicResponse.message;
} }
response.allResponses.push(); // execute callback
} }
return results; return response;
} }
delUser (user, params) {} delUser (user, params) {}

View File

@ -1,6 +1,6 @@
import { readFileSync, writeFileSync } from "fs"; import { readFileSync, writeFileSync } from "fs";
import { exit } from "process"; import { exit } from "process";
import { DB_BACKEND } from "./backends.js"; import { AtomicChange, DB_BACKEND, doNothingCallback } from "./backends.js";
export default class LocalDB extends DB_BACKEND { export default class LocalDB extends DB_BACKEND {
#path = null; #path = null;
@ -35,22 +35,7 @@ export default class LocalDB extends DB_BACKEND {
writeFileSync(this.#path, JSON.stringify(this.#data)); writeFileSync(this.#path, JSON.stringify(this.#data));
} }
addUser (user, attributes, params) { addUser (user, attributes, params) {}
const username = `${user.id}@${user.realm}`;
if (this.#data.users[username]) { // user already exists
return {
ok: false,
status: 1,
message: "User already exists"
};
}
else {
attributes = attributes || this.#defaultuser;
this.#data.users[username] = attributes;
this.#save();
return null;
}
}
getUser (user, params) { getUser (user, params) {
const requestedUser = `${user.id}@${user.realm}`; const requestedUser = `${user.id}@${user.realm}`;
@ -76,33 +61,42 @@ export default class LocalDB extends DB_BACKEND {
} }
setUser (user, attributes, params) { setUser (user, attributes, params) {
if (attributes.resources && attributes.cluster && attributes.templates) { // localdb should only deal with these attributes if (attributes.resources && attributes.cluster && attributes.templates) {
const username = `${user.id}@${user.realm}`; const username = `${user.id}@${user.realm}`;
if (this.#data.users[username]) { if (this.#data.users[username]) {
this.#data.users[username] = attributes; 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(); this.#save();
return true; return { ok: true, status: 200, message: "" };
},
{ ok: true, status: 200, message: "" }
);
} }
else { else {
return false; return new AtomicChange(false, {}, doNothingCallback, { ok: false, status: 401, message: `${params.username} is not an admin user in localdb` });
} }
} }
else { // if request is not setting these attributes, then assume its fine but do nothing else {
return true; // return false;
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) { delUser (user, params) {}
const username = `${user.id}@${user.realm}`;
if (this.#data.users[username]) {
delete this.#data.users[username];
this.#save();
return true;
}
else {
return false;
}
}
// group methods not implemented because db backend does not store groups // group methods not implemented because db backend does not store groups
addGroup (group, atrributes, params) {} addGroup (group, atrributes, params) {}
@ -115,26 +109,8 @@ export default class LocalDB extends DB_BACKEND {
delGroup (group, params) {} delGroup (group, params) {}
// assume that adding to group also adds to group's pool // assume that adding to group also adds to group's pool
addUserToGroup (user, group, params) { addUserToGroup (user, group, params) {}
const username = `${user.id}@${user.realm}`;
if (this.#data.users[username]) {
this.#data.users[username].cluster.pools[group.id] = true;
return true;
}
else {
return false;
}
}
// assume that adding to group also adds to group's pool // assume that adding to group also adds to group's pool
delUserFromGroup (user, group, params) { delUserFromGroup (user, group, params) {}
const username = `${user.id}@${user.realm}`;
if (this.#data.users[username] && this.#data.users[username].cluster.pools[group.id]) {
delete this.#data.users[username].cluster.pools[group.id];
return true;
}
else {
return false;
}
}
} }

View File

@ -1,5 +1,5 @@
import axios from "axios"; import axios from "axios";
import { AUTH_BACKEND } from "./backends.js"; import { AtomicChange, AUTH_BACKEND, doNothingCallback } from "./backends.js";
import * as setCookie from "set-cookie-parser"; import * as setCookie from "set-cookie-parser";
export default class PAASLDAP extends AUTH_BACKEND { export default class PAASLDAP extends AUTH_BACKEND {
@ -48,17 +48,12 @@ export default class PAASLDAP extends AUTH_BACKEND {
} }
#handleGenericReturn (res) { #handleGenericReturn (res) {
if (res.ok) { // if ok, return null
return null;
}
else { // if not ok, return error obj
return { return {
ok: res.ok, ok: res.ok,
status: res.status, status: res.status,
message: res.ok ? "" : res.data.error message: res.ok ? "" : res.data.error
}; };
} }
}
async openSession (user, password) { async openSession (user, password) {
const username = user.id; const username = user.id;
@ -86,10 +81,7 @@ export default class PAASLDAP extends AUTH_BACKEND {
} }
} }
async addUser (user, attributes, params) { async addUser (user, attributes, params) {}
const res = await this.#request(`/users/${user.id}`, "POST", params, attributes);
return this.#handleGenericReturn(res);
}
async getUser (user, params) { async getUser (user, params) {
if (!params) { // params required, do nothing if params are missing if (!params) { // params required, do nothing if params are missing
@ -124,19 +116,37 @@ export default class PAASLDAP extends AUTH_BACKEND {
} }
async setUser (user, attributes, params) { async setUser (user, attributes, params) {
const res = await this.#request(`/users/${user.id}`, "POST", params, attributes); 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); return this.#handleGenericReturn(res);
},
{ ok: true, status: 200, message: "" }
);
} }
async delUser (user, params) { async delUser (user, params) {}
const res = await this.#request(`/users/${user.id}`, "DELETE", params);
return this.#handleGenericReturn(res);
}
async addGroup (group, attributes, params) { async addGroup (group, attributes, params) {}
const res = await this.#request(`/groups/${group.id}`, "POST", params);
return this.#handleGenericReturn(res);
}
async getGroup (group, params) { async getGroup (group, params) {
return await this.#request(`/groups/${group.id}`, "GET", params); return await this.#request(`/groups/${group.id}`, "GET", params);
@ -163,21 +173,12 @@ export default class PAASLDAP extends AUTH_BACKEND {
async setGroup (group, attributes, params) { async setGroup (group, attributes, params) {
// not implemented, LDAP groups do not have any attributes to change // not implemented, LDAP groups do not have any attributes to change
return null; return new AtomicChange(true, {}, doNothingCallback, null); ;
} }
async delGroup (group, params) { async delGroup (group, params) {}
const res = await this.#request(`/groups/${group.id}`, "DELETE", params);
return this.#handleGenericReturn(res);
}
async addUserToGroup (user, group, params) { async addUserToGroup (user, group, params) {}
const res = await this.#request(`/groups/${group.id}/members/${user.id}`, "POST", params);
return this.#handleGenericReturn(res);
}
async delUserFromGroup (user, group, params) { async delUserFromGroup (user, group, params) {}
const res = await this.#request(`/groups/${group.id}/members/${user.id}`, "DELETE", params);
return this.#handleGenericReturn(res);
}
} }