From 10116da900e654ae4c528847f2ba9eb452554a05 Mon Sep 17 00:00:00 2001 From: Arthur Lu Date: Fri, 17 Nov 2023 19:49:11 +0000 Subject: [PATCH] fix openldap init script paas user token saving, add config file with BASE_DN, add async wrapper class for ldap client, implement addUser getUser delUser, add and implement addGroup delGroup methods --- .gitignore | 3 +- config/config.template.json | 3 + openldap/init.sh | 2 +- src/config.js | 11 +++ src/ldap.js | 184 ++++++++++++++++++++++++++++++++---- src/main.js | 49 +++++++--- src/package.js | 11 +++ 7 files changed, 232 insertions(+), 31 deletions(-) create mode 100644 config/config.template.json create mode 100644 src/config.js create mode 100644 src/package.js diff --git a/.gitignore b/.gitignore index 7fd5b7c..fef9885 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ **/package-lock.json **/node_modules -**/*.token \ No newline at end of file +**/*.token +**/config.json \ No newline at end of file diff --git a/config/config.template.json b/config/config.template.json new file mode 100644 index 0000000..971873f --- /dev/null +++ b/config/config.template.json @@ -0,0 +1,3 @@ +{ + "basedn": "dc=example,dc=com" +} \ No newline at end of file diff --git a/openldap/init.sh b/openldap/init.sh index 7d57605..5131654 100755 --- a/openldap/init.sh +++ b/openldap/init.sh @@ -2,7 +2,7 @@ export BASE_DN='' read -p "Base DN: " BASE_DN export PAAS_PASSWD=$(tr -dc 'A-Za-z0-9!"#$%&'\''()*+,-./:;<=>?@[\]^_`{|}~' paas.token +echo "$PAAS_PASSWD" -n > paas.token echo "Saved PAAS Authentication Token (password) to paas.token" envsubst '$BASE_DN' < auth.template.ldif > auth.ldif diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..d9158a1 --- /dev/null +++ b/src/config.js @@ -0,0 +1,11 @@ +import { readFileSync } from "fs"; +import { exit } from "process"; +export default () => { + try { + return JSON.parse(readFileSync(global.argv.configPath)); + } + catch (e) { + console.log(`Error: ${global.argv.configPath} was not found. Please follow the directions in the README to initialize localdb.json.`); + exit(1); + } +}; diff --git a/src/ldap.js b/src/ldap.js index 25de68a..ef6ee9b 100644 --- a/src/ldap.js +++ b/src/ldap.js @@ -1,31 +1,179 @@ import ldap from "ldapjs"; -import { exit } from "process"; -export class LDAP { +export default class LDAP { #client = null; - #paasBind = null; - #baseDN = null; + #basedn = null; + #peopledn = null; + #groupsdn = null; - constructor (url, paasBind, baseDN) { + constructor (url, basedn) { const opts = { url }; - this.#client = ldap.createClient(opts); - this.#client.on("connectError", (err) => { - console.err(`Error: could not establish connection to ${url}`); - console.err(err); - exit(1); - }); - - this.#paasBind = paasBind; - this.#baseDN = baseDN; + this.#client = new LDAPJS_CLIENT_ASYNC_WRAPPER(opts); + this.#basedn = basedn; + this.#peopledn = `ou=people,${basedn}`; + this.#groupsdn = `ou=groups,${basedn}`; } - addUser (uid, entry) {} + async addUser (bind, uid, attrs) { + const result = await this.#client.bind(bind.dn, bind.password); + if (!result.ok) { + return result; + } + const userDN = `uid=${uid},${this.#peopledn}`; + const entry = { + objectClass: "inetOrgPerson", + cn: attrs.cn, + sn: attrs.sn, + uid, + userPassword: attrs.userPassword + }; + return await this.#client.add(userDN, entry); + } - getUser (uid) {} + async getUser (bind, uid) { + const result = await this.#client.bind(bind.dn, bind.password); + if (!result.ok) { + return result; + } + const opts = { + filter: `(uid=${uid})`, + scope: "sub" + }; + return await this.#client.search(this.#peopledn, opts); + } - modUser (uid, entry) {} + async modUser (bind, uid, attrs) { } - delUser (uid) {} + async delUser (bind, uid) { + const result = await this.#client.bind(bind.dn, bind.password); + if (!result.ok) { + return result; + } + const userDN = `uid=${uid},${this.#peopledn}`; + return await this.#client.del(userDN); + } + + async addGroup (bind, gid, attrs) { + const result = await this.#client.bind(bind.dn, bind.password); + if (!result.ok) { + return result; + } + const groupDN = `cn=${gid},${this.#groupsdn}`; + const entry = { + objectClass: "groupOfNames", + member: "", + cn: gid + }; + return await this.#client.add(groupDN, entry); + } + + async delGroup (bind, gid) { + const result = await this.#client.bind(bind.dn, bind.password); + if (!result.ok) { + return result; + } + const groupDN = `cn=${gid},${this.#groupsdn}`; + return await this.#client.del(groupDN); + } + + async addUserToGroup (bind, uid, gid) { + + } + + async delUserFromGroup (bind, uid, gid) { } +} + +class LDAPJS_CLIENT_ASYNC_WRAPPER { + #client = null; + constructor (opts) { + this.#client = ldap.createClient(opts); + this.#client.on("error", (err) => { + console.error(`An error occured:\n${err}`); + }); + this.#client.on("connectError", (err) => { + console.error(`Unable to connect to ${opts.url}:\n${err}`); + }); + } + + bind (dn, password) { + return new Promise((resolve) => { + this.#client.bind(dn, password, (err) => { + if (err) { + resolve({ ok: false, error: err }); + } + else { + resolve({ ok: true }); + } + }); + }); + } + + add (dn, entry) { + return new Promise((resolve) => { + this.#client.add(dn, entry, (err) => { + if (err) { + resolve({ ok: false, error: err }); + } + else { + resolve({ ok: true }); + } + }); + }); + } + + search (base, options) { + return new Promise((resolve) => { + this.#client.search(base, options, (err, res) => { + if (err) { + return resolve({ ok: false, error: err }); + } + const results = { ok: false, status: 1, message: "", entries: [] }; + res.on("searchRequest", (searchRequest) => { }); + res.on("searchEntry", (entry) => { + results.entries.push({ dn: entry.pojo.objectName, attributes: entry.pojo.attributes }); + }); + res.on("searchReference", (referral) => { }); + res.on("error", (error) => { + results.ok = error.status === 0; + results.status = error.status; + results.message = error.message; + resolve(results); + }); + res.on("end", (result) => { + results.ok = result.status === 0; + results.status = result.status; + results.message = result.message; + resolve(results); + }); + }); + }); + } + + modify (name, changes) { + return new Promise((resolve) => { + this.#client.modify(name, changes, (err) => { + if (err) { + resolve({ ok: false, error: err }); + } + else { + resolve({ ok: true }); + } + }); + }); + } + + del (dn) { + return new Promise((resolve) => { + this.#client.del(dn, (err) => { + if (err) { + resolve({ ok: false, error: err }); + } + else { + resolve({ ok: true }); + } + }); + }); + } } diff --git a/src/main.js b/src/main.js index c6fda35..2f15802 100644 --- a/src/main.js +++ b/src/main.js @@ -2,29 +2,56 @@ import express from "express"; import bodyParser from "body-parser"; import cookieParser from "cookie-parser"; import morgan from "morgan"; -import LDAP from "ldap.js"; + +import LDAP from "./ldap.js"; +import _config from "./config.js"; +import _package from "./package.js"; + +import parseArgs from "minimist"; + +global.argv = parseArgs(process.argv.slice(2), { + default: { + package: "package.json", + listenPort: 8082, + ldapURL: "ldap://localhost", + configPath: "config/config.json" + } +}); + +global.package = _package(global.argv.package); +global.config = _config(global.argv.configPath); + +const ldap = new LDAP(global.argv.ldapURL, global.config.basedn); + +/* import { readFileSync } from "fs"; +const paas = { + dn: `uid=paas,ou=people,${global.config.basedn}`, + password: readFileSync("paas.token").toString() +}; +console.log(await ldap.addUser(paas, "testuser", { cn: "test", sn: "test", userPassword: "test" })); +console.log((await ldap.getUser(paas, "testuser")).entries[0].attributes); +console.log(await ldap.delUser(paas, "testuser")); +console.log(await ldap.addGroup(paas, "testgroup")); +console.log(await ldap.delGroup(paas, "testgroup")); +exit(0); */ const app = express(); app.use(bodyParser.urlencoded({ extended: true })); app.use(cookieParser()); app.use(morgan("combined")); -app.listen(global.db.listenPort, () => { - console.log(`proxmoxaas-api v${global.api.version} listening on port ${global.db.listenPort}`); +app.listen(global.argv.listenPort, () => { + console.log(`proxmoxaas-ldap v${global.package.version} listening on port ${global.argv.listenPort}`); }); -app.get("/:user", (req, res) => { - +app.get("/:user", async (req, res) => { }); -app.post("/:user", (req, res) => { - +app.post("/:user", async (req, res) => { }); -app.delete("/:user", (req, res) => { - +app.delete("/:user", async (req, res) => { }); -app.post("/:user/password", (req, res) => { - +app.post("/:user/password", async (req, res) => { }); diff --git a/src/package.js b/src/package.js new file mode 100644 index 0000000..fa00582 --- /dev/null +++ b/src/package.js @@ -0,0 +1,11 @@ +import { readFileSync } from "fs"; +import { exit } from "process"; +export default (path) => { + try { + return JSON.parse(readFileSync(path)); + } + catch (e) { + console.log(`Error: ${path} was not found.`); + exit(1); + } +};