const ldapjs = require("ldapjs"); import ConfigManager, { LDAPAttribute, LDAPCategory } from "./ConfigLoader"; import { LDAPSchemaAttribute, LDAPSchemaObjectClass } from './LDAPSchema'; import LDAPEntry from "./LDAPEntry"; import LDAPTree from "./LDAPTree"; interface ILDAPManager { Release(): Promise; /** * Try to login as user to check password * @param dn * @param password */ TryBind(dn: string, password: string): Promise; CheckLoginExists(user: string): Promise; ListEntries(category: LDAPCategory): Promise; /** @return all dn */ GetTree(): Promise; GetEntry(dn: string): Promise>; Remove(dn: string): Promise; GetSchema(): Promise>; } class LDAPManager implements ILDAPManager { protected cli: any; public constructor(cli: any) { this.cli = cli; } public static Create(): Promise { return new Promise((ok, ko) => { let config = ConfigManager.GetInstance(); let cli = ldapjs.createClient({ url: config.GetLDAPUrls() }); cli.on('error', (err: any) => { console.error("LDAP general error: ", err); ko(err); }); cli.bind(config.GetBindDn(), config.GetBindPassword(), (err: any) => { if (err) { console.error("LDAP bind error: ", err); ko(err); } else { ok(new LDAPManager(cli)); } }); }); } public TryBind(userDn: string, password: string): Promise { return new Promise((ok, ko) => { let cli = ldapjs.createClient({ url: ConfigManager.GetInstance().GetLDAPUrls() }); cli.on('error', (err: any) => { console.error("LDAP general error: ", err); ko(err); }); cli.bind(userDn, password, (err: any) => { if (err) { ok(false); } else { ok(true); } }); }); } private Search(base: string, scope: string, filter: string|undefined =undefined, attributes: string[]|undefined =undefined): Promise>> { return new Promise((ok, ko) => { this.cli.search(base, { scope: scope, filter: filter, attributes: attributes || ['*', 'memberof'], paged: false }, (err: any, res: any) => { if (err) { console.error("Search error: ", { base: base, scope: scope, filter: filter, attributes: attributes }); ko(err); return; } let result = new Map(); let error = false; res.on('searchEntry', (i: any) => { if (error) return; let LDAPEntry = new Map(); for (let attr in i.object) { let value = i.object[attr]; if (Array.isArray(value)) { let arr = new Array(); for (let j of value) arr.push(j); value = arr; } else { let arr = new Array(); arr.push(value); value = arr; } LDAPEntry.set(attr, value); } result.set(i.dn, LDAPEntry); }); res.on('error', (err: any) => { error = true; console.error("Search error: ", { base: base, scope: scope, filter: filter, attributes: attributes }); ko(err); }); res.on('end', () => { if (error) return; ok(result); }); }); }); } public GetEntry(dn: string): Promise> { return new Promise((ok, ko) => { this.Search(dn, "sub").then(result => { for (let [_, val] of result) { ok(val); return; } }).catch(err => { ko(err); }); }); } public CheckLoginExists(user: string): Promise { return new Promise((ok, ko) => { let config = ConfigManager.GetInstance(); return this.Search(config.GetLoginBase(), config.GetLoginScope(), config.GetLoginFilter(user), []).then(result => { if (!result || result.size !== 1) ko(); for (let [addr, _] of result) { ok(addr); } }); }); } public GetTree(): Promise { return new Promise(ok => { const rootDn: string = ConfigManager.GetInstance().GetLDAPRoot(); this.Search(rootDn, "sub", undefined, ["dn", "ObjectClass"]).then(searchDns => { let result = LDAPTree.CreateRoot(); searchDns.forEach((val, dn) => { let classes = val.get("objectClass"); classes && result.push(dn, classes); }); ok(result.Compress()); }); }); } public ListEntries(category: LDAPCategory): Promise { return new Promise((ok, _) => { this.Search(category.GetBaseDn(), category.GetScope(), category.GetFilter() || undefined, category.GetAttributes().map(i => i.mapped)).then(result => { let userArray = new Array(); for (let [addr, val] of result) userArray.push(new LDAPEntry(category.GetAttributes(), addr, val)); ok(userArray); }); }); } public Release():Promise { return new Promise(ok => { this.cli.unbind((err: any) => { if (err) console.error("LDAP unbind error: ", err); ok(); }); }); } public Remove(dn: string): Promise { return new Promise((ok, ko) => { this.cli.del(dn, (err: any) => { if (err) ko(err["message"]); else ok(); }); }); } public GetSchema(): Promise> { return new Promise((ok, ko) => { const rootDn = ConfigManager.GetInstance().GetLDAPRoot(); let subschema: string; this.Search(rootDn, "base", undefined, ["subschemaSubentry"]).then(result => { const baseItem = result?.get(rootDn)?.get("subschemaSubentry"); const _subschema = baseItem && baseItem.length ? baseItem[0] : null; if (!_subschema) { console.error("Cannot find schema for base " + rootDn); ko("Schema not found"); return; } subschema = _subschema; return this.Search(subschema, "base", undefined, ["attributeTypes", "objectClasses"]); }).then(result => { const attributesArr = result?.get(subschema)?.get("attributeTypes"); const classesArr = result?.get(subschema)?.get("objectClasses"); if (!attributesArr || !classesArr) { console.error("Cannot find schema definition for " + rootDn); ko("Schema definition not found"); return; } let attributes: Map = new Map(); let schemas: Map = new Map(); for (let i of attributesArr) { let attr = new LDAPSchemaAttribute(i); attributes.set(attr.GetName(), attr); } for (let i of classesArr) { let schema = new LDAPSchemaObjectClass(i, attributes); schemas.set(schema.GetName(), schema); } for (let [_, schema] of schemas) schema.Consolidate(schemas, attributes); ok(schemas); }).catch(err => { console.error("Failed to retreive schema: ", err); ko(err); }); }); } } export default class LDAPFactory { private _instance: ILDAPManager|null = null; public GetInstance(): Promise { if (!this._instance) { return new Promise((ok, ko) => { LDAPManager.Create().then(inst => { this._instance = inst; ok(this._instance); }); }); } return Promise.resolve(this._instance); } public Release(): Promise { if (this._instance) return this._instance.Release().then(() => this._instance = null); return Promise.resolve(null); } } export { ILDAPManager };