@kronos-integration/service-ldap
Version:
200 lines (167 loc) • 4.34 kB
JavaScript
import { Client, Change, Attribute } from "ldapts";
import {
prepareAttributesDefinitions,
url_attribute,
default_attribute
} from "pacc";
import { Service } from "@kronos-integration/service";
import { expand } from "./util.mjs";
/**
* LDAP
*/
export class ServiceLDAP extends Service {
/**
* @return {string} 'ldap'
*/
static get name() {
return "ldap";
}
static get description() {
return "LDAP server access for bind/add/modify/del/query";
}
static attributes = prepareAttributesDefinitions(
{
url: {
...url_attribute,
needsRestart: true,
mandatory: true
},
entitlements: {
description: "attributes to build a entitlement query",
attributes: {
bindDN: default_attribute,
base: default_attribute,
attribute: { ...default_attribute, default: "cn" },
scope: { ...default_attribute, default: "sub" },
filter: default_attribute
}
}
},
Service.attributes
);
static get endpoints() {
return {
...super.endpoints,
authenticate: {
receive: "authenticate"
},
add: {
receive: "add"
},
del: {
receive: "del"
},
modify: {
receive: "modify"
},
search: {
receive: "search"
}
};
}
async prepareRequest(request) {
const client = new Client({ url: this.url });
if (request.bind) {
await client.bind(request.bind.dn, request.bind.password);
}
return client;
}
async add(request) {
let client;
try {
client = await this.prepareRequest(request);
await client.add(request.dn, request.entry);
} finally {
if (client) {
await client.unbind();
}
}
}
async del(request) {
let client;
try {
client = await this.prepareRequest(request);
await client.del(request.dn);
} finally {
if (client) {
await client.unbind();
}
}
}
async modify(request) {
let client;
try {
client = await this.prepareRequest(request);
const changes = Object.entries(request.replace).map(
([type, values]) =>
new Change({
operation: "replace",
modification: new Attribute({
type,
values: asArray(values)
})
})
);
console.log(request.dn, changes);
await client.modify(request.dn, changes);
} finally {
if (client) {
await client.unbind();
}
}
}
/**
* Execute a query.
* @param {Object} request
* @return {Promise<Object>} result
*/
async search(request) {
let client;
try {
client = await this.prepareRequest(request);
const json = await client.search(request.base, request);
return json.searchEntries;
} finally {
if (client) {
await client.unbind();
}
}
}
/**
* Authorize with username and password.
* @param {Object} props
* @param {string} props.username
* @param {string} props.password
* @return {Promise<Set<string>>} entitlements
*/
async authenticate(props) {
const { username, password } = props;
const client = new Client({ url: this.url });
const values = {
username
};
try {
const bindDN = expand(this.entitlements.bindDN, values);
this.trace(`bind ${bindDN} (${this.entitlements.bindDN})`);
await client.bind(bindDN, password);
const attribute = expand(this.entitlements.attribute, values);
const searchOptions = {
scope: this.entitlements.scope,
filter: expand(this.entitlements.filter, values),
attributes: [attribute]
};
const base = expand(this.entitlements.base, values);
this.trace(`search ${base} ${JSON.stringify(searchOptions)}`);
const { searchEntries } = await client.search(base, searchOptions);
const entitlements = new Set(searchEntries.map(e => e[attribute]));
this.trace(`entitlements ${[...entitlements]}`);
return { username, entitlements };
} finally {
await client.unbind();
}
}
}
function asArray(value) {
return Array.isArray(value) ? value : value === undefined ? [] : [value];
}
export default ServiceLDAP;