diff --git a/src/backends/backends.js b/src/backends/backends.js index ae83be9..32379f5 100644 --- a/src/backends/backends.js +++ b/src/backends/backends.js @@ -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. * Not all backends need to implement all interface methods. */ 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 {Object} attributes user attributes * @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) {} @@ -84,28 +106,31 @@ class USER_BACKEND extends BACKEND { 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 {Object} attributes new user attributes to modify * @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) {} /** - * 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 {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) {} /** - * Add group to backend - * @param {{id: string}} group + * Validate an add 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}} group * @param {Object} attributes group attributes * @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) {} @@ -125,37 +150,40 @@ class USER_BACKEND extends BACKEND { getAllGroups (params) {} /** - * Modify group in backend - * @param {{id: string}} group - * @param {Object} attributes new group attributes to modify + * Validate a set 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}} group + * @param {Object} attributes group attributes * @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) {} - /** - * Delete group from backend - * @param {{id: string}} group + * 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. + * @param {{id: string, realm: string}} group * @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}} group * @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) {} /** - * 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}} group * @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) {} } @@ -218,20 +246,30 @@ class USER_BACKEND_MANAGER extends USER_BACKEND { } 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, status: 200, - message: "" + message: "", + allResponses: [] }; - for (const backend of this.#config.realm[user.realm]) { - const result = await global.backends[backend].setUser(user, attributes, params); - if (!result) { - results.ok = false; - results.status = 500; - return results; + 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 results; + return response; } delUser (user, params) {} diff --git a/src/backends/localdb.js b/src/backends/localdb.js index 79242a2..922ca4f 100644 --- a/src/backends/localdb.js +++ b/src/backends/localdb.js @@ -1,6 +1,6 @@ import { readFileSync, writeFileSync } from "fs"; 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 { #path = null; @@ -35,22 +35,7 @@ export default class LocalDB extends DB_BACKEND { writeFileSync(this.#path, JSON.stringify(this.#data)); } - 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; - } - } + addUser (user, attributes, params) {} getUser (user, params) { const requestedUser = `${user.id}@${user.realm}`; @@ -76,33 +61,42 @@ export default class LocalDB extends DB_BACKEND { } 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}`; if (this.#data.users[username]) { - this.#data.users[username] = attributes; - this.#save(); - return true; + 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 false; + // return false; + return new AtomicChange(false, {}, doNothingCallback, { ok: false, status: 400, message: `${username} was not found in localdb` }); } } - else { // if request is not setting these attributes, then assume its fine but do nothing - return true; + else { + return new AtomicChange(true, {}, doNothingCallback, null); } } - 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; - } - } + delUser (user, params) {} // group methods not implemented because db backend does not store groups addGroup (group, atrributes, params) {} @@ -115,26 +109,8 @@ export default class LocalDB extends DB_BACKEND { delGroup (group, params) {} // assume that adding to group also adds to group's pool - 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; - } - } + addUserToGroup (user, group, params) {} // assume that adding to group also adds to group's pool - 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; - } - } + delUserFromGroup (user, group, params) {} } diff --git a/src/backends/paasldap.js b/src/backends/paasldap.js index 0625f03..d2779a0 100644 --- a/src/backends/paasldap.js +++ b/src/backends/paasldap.js @@ -1,5 +1,5 @@ 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"; export default class PAASLDAP extends AUTH_BACKEND { @@ -48,16 +48,11 @@ export default class PAASLDAP extends AUTH_BACKEND { } #handleGenericReturn (res) { - if (res.ok) { // if ok, return null - return null; - } - else { // if not ok, return error obj - return { - ok: res.ok, - status: res.status, - message: res.ok ? "" : res.data.error - }; - } + return { + ok: res.ok, + status: res.status, + message: res.ok ? "" : res.data.error + }; } async openSession (user, password) { @@ -86,10 +81,7 @@ export default class PAASLDAP extends AUTH_BACKEND { } } - async addUser (user, attributes, params) { - const res = await this.#request(`/users/${user.id}`, "POST", params, attributes); - return this.#handleGenericReturn(res); - } + async addUser (user, attributes, params) {} async getUser (user, params) { 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) { - const res = await this.#request(`/users/${user.id}`, "POST", params, attributes); - return this.#handleGenericReturn(res); + 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) { - const res = await this.#request(`/users/${user.id}`, "DELETE", params); - return this.#handleGenericReturn(res); - } + async delUser (user, params) {} - async addGroup (group, attributes, params) { - const res = await this.#request(`/groups/${group.id}`, "POST", params); - return this.#handleGenericReturn(res); - } + async addGroup (group, attributes, params) {} async getGroup (group, 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) { // not implemented, LDAP groups do not have any attributes to change - return null; + return new AtomicChange(true, {}, doNothingCallback, null); ; } - async delGroup (group, params) { - const res = await this.#request(`/groups/${group.id}`, "DELETE", params); - return this.#handleGenericReturn(res); - } + async delGroup (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 addUserToGroup (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); - } + async delUserFromGroup (user, group, params) {} }