First commit
This commit is contained in:
625
node_modules/ldapjs/docs/examples.md
generated
vendored
Normal file
625
node_modules/ldapjs/docs/examples.md
generated
vendored
Normal file
@ -0,0 +1,625 @@
|
||||
---
|
||||
title: Examples | ldapjs
|
||||
---
|
||||
|
||||
# ldapjs Examples
|
||||
|
||||
<div class="intro">
|
||||
|
||||
This page contains a (hopefully) growing list of sample code to get you started
|
||||
with ldapjs.
|
||||
|
||||
</div>
|
||||
|
||||
# In-memory server
|
||||
|
||||
```js
|
||||
const ldap = require('ldapjs');
|
||||
|
||||
|
||||
///--- Shared handlers
|
||||
|
||||
function authorize(req, res, next) {
|
||||
/* Any user may search after bind, only cn=root has full power */
|
||||
const isSearch = (req instanceof ldap.SearchRequest);
|
||||
if (!req.connection.ldap.bindDN.equals('cn=root') && !isSearch)
|
||||
return next(new ldap.InsufficientAccessRightsError());
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
///--- Globals
|
||||
|
||||
const SUFFIX = 'o=joyent';
|
||||
const db = {};
|
||||
const server = ldap.createServer();
|
||||
|
||||
|
||||
|
||||
server.bind('cn=root', (req, res, next) => {
|
||||
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
|
||||
return next(new ldap.InvalidCredentialsError());
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
server.add(SUFFIX, authorize, (req, res, next) => {
|
||||
const dn = req.dn.toString();
|
||||
|
||||
if (db[dn])
|
||||
return next(new ldap.EntryAlreadyExistsError(dn));
|
||||
|
||||
db[dn] = req.toObject().attributes;
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
server.bind(SUFFIX, (req, res, next) => {
|
||||
const dn = req.dn.toString();
|
||||
if (!db[dn])
|
||||
return next(new ldap.NoSuchObjectError(dn));
|
||||
|
||||
if (!db[dn].userpassword)
|
||||
return next(new ldap.NoSuchAttributeError('userPassword'));
|
||||
|
||||
if (db[dn].userpassword.indexOf(req.credentials) === -1)
|
||||
return next(new ldap.InvalidCredentialsError());
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
server.compare(SUFFIX, authorize, (req, res, next) => {
|
||||
const dn = req.dn.toString();
|
||||
if (!db[dn])
|
||||
return next(new ldap.NoSuchObjectError(dn));
|
||||
|
||||
if (!db[dn][req.attribute])
|
||||
return next(new ldap.NoSuchAttributeError(req.attribute));
|
||||
|
||||
const matches = false;
|
||||
const vals = db[dn][req.attribute];
|
||||
for (const value of vals) {
|
||||
if (value === req.value) {
|
||||
matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
res.end(matches);
|
||||
return next();
|
||||
});
|
||||
|
||||
server.del(SUFFIX, authorize, (req, res, next) => {
|
||||
const dn = req.dn.toString();
|
||||
if (!db[dn])
|
||||
return next(new ldap.NoSuchObjectError(dn));
|
||||
|
||||
delete db[dn];
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
server.modify(SUFFIX, authorize, (req, res, next) => {
|
||||
const dn = req.dn.toString();
|
||||
if (!req.changes.length)
|
||||
return next(new ldap.ProtocolError('changes required'));
|
||||
if (!db[dn])
|
||||
return next(new ldap.NoSuchObjectError(dn));
|
||||
|
||||
const entry = db[dn];
|
||||
|
||||
for (const change of req.changes) {
|
||||
mod = change.modification;
|
||||
switch (change.operation) {
|
||||
case 'replace':
|
||||
if (!entry[mod.type])
|
||||
return next(new ldap.NoSuchAttributeError(mod.type));
|
||||
|
||||
if (!mod.vals || !mod.vals.length) {
|
||||
delete entry[mod.type];
|
||||
} else {
|
||||
entry[mod.type] = mod.vals;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'add':
|
||||
if (!entry[mod.type]) {
|
||||
entry[mod.type] = mod.vals;
|
||||
} else {
|
||||
for (const v of mod.vals) {
|
||||
if (entry[mod.type].indexOf(v) === -1)
|
||||
entry[mod.type].push(v);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
if (!entry[mod.type])
|
||||
return next(new ldap.NoSuchAttributeError(mod.type));
|
||||
|
||||
delete entry[mod.type];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
server.search(SUFFIX, authorize, (req, res, next) => {
|
||||
const dn = req.dn.toString();
|
||||
if (!db[dn])
|
||||
return next(new ldap.NoSuchObjectError(dn));
|
||||
|
||||
let scopeCheck;
|
||||
|
||||
switch (req.scope) {
|
||||
case 'base':
|
||||
if (req.filter.matches(db[dn])) {
|
||||
res.send({
|
||||
dn: dn,
|
||||
attributes: db[dn]
|
||||
});
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
|
||||
case 'one':
|
||||
scopeCheck = (k) => {
|
||||
if (req.dn.equals(k))
|
||||
return true;
|
||||
|
||||
const parent = ldap.parseDN(k).parent();
|
||||
return (parent ? parent.equals(req.dn) : false);
|
||||
};
|
||||
break;
|
||||
|
||||
case 'sub':
|
||||
scopeCheck = (k) => {
|
||||
return (req.dn.equals(k) || req.dn.parentOf(k));
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
const keys = Object.keys(db);
|
||||
for (const key of keys) {
|
||||
if (!scopeCheck(key))
|
||||
return;
|
||||
|
||||
if (req.filter.matches(db[key])) {
|
||||
res.send({
|
||||
dn: key,
|
||||
attributes: db[key]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
|
||||
|
||||
///--- Fire it up
|
||||
|
||||
server.listen(1389, () => {
|
||||
console.log('LDAP server up at: %s', server.url);
|
||||
});
|
||||
```
|
||||
|
||||
# /etc/passwd server
|
||||
|
||||
```js
|
||||
const fs = require('fs');
|
||||
const ldap = require('ldapjs');
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
|
||||
|
||||
///--- Shared handlers
|
||||
|
||||
function authorize(req, res, next) {
|
||||
if (!req.connection.ldap.bindDN.equals('cn=root'))
|
||||
return next(new ldap.InsufficientAccessRightsError());
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
function loadPasswdFile(req, res, next) {
|
||||
fs.readFile('/etc/passwd', 'utf8', (err, data) => {
|
||||
if (err)
|
||||
return next(new ldap.OperationsError(err.message));
|
||||
|
||||
req.users = {};
|
||||
|
||||
const lines = data.split('\n');
|
||||
for (const line of lines) {
|
||||
if (!line || /^#/.test(line))
|
||||
continue;
|
||||
|
||||
const record = line.split(':');
|
||||
if (!record || !record.length)
|
||||
continue;
|
||||
|
||||
req.users[record[0]] = {
|
||||
dn: 'cn=' + record[0] + ', ou=users, o=myhost',
|
||||
attributes: {
|
||||
cn: record[0],
|
||||
uid: record[2],
|
||||
gid: record[3],
|
||||
description: record[4],
|
||||
homedirectory: record[5],
|
||||
shell: record[6] || '',
|
||||
objectclass: 'unixUser'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const pre = [authorize, loadPasswdFile];
|
||||
|
||||
|
||||
|
||||
///--- Mainline
|
||||
|
||||
const server = ldap.createServer();
|
||||
|
||||
server.bind('cn=root', (req, res, next) => {
|
||||
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
|
||||
return next(new ldap.InvalidCredentialsError());
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
|
||||
server.add('ou=users, o=myhost', pre, (req, res, next) => {
|
||||
if (!req.dn.rdns[0].cn)
|
||||
return next(new ldap.ConstraintViolationError('cn required'));
|
||||
|
||||
if (req.users[req.dn.rdns[0].cn])
|
||||
return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
|
||||
|
||||
const entry = req.toObject().attributes;
|
||||
|
||||
if (entry.objectclass.indexOf('unixUser') === -1)
|
||||
return next(new ldap.ConstraintViolationError('entry must be a unixUser'));
|
||||
|
||||
const opts = ['-m'];
|
||||
if (entry.description) {
|
||||
opts.push('-c');
|
||||
opts.push(entry.description[0]);
|
||||
}
|
||||
if (entry.homedirectory) {
|
||||
opts.push('-d');
|
||||
opts.push(entry.homedirectory[0]);
|
||||
}
|
||||
if (entry.gid) {
|
||||
opts.push('-g');
|
||||
opts.push(entry.gid[0]);
|
||||
}
|
||||
if (entry.shell) {
|
||||
opts.push('-s');
|
||||
opts.push(entry.shell[0]);
|
||||
}
|
||||
if (entry.uid) {
|
||||
opts.push('-u');
|
||||
opts.push(entry.uid[0]);
|
||||
}
|
||||
opts.push(entry.cn[0]);
|
||||
const useradd = spawn('useradd', opts);
|
||||
|
||||
const messages = [];
|
||||
|
||||
useradd.stdout.on('data', (data) => {
|
||||
messages.push(data.toString());
|
||||
});
|
||||
useradd.stderr.on('data', (data) => {
|
||||
messages.push(data.toString());
|
||||
});
|
||||
|
||||
useradd.on('exit', (code) => {
|
||||
if (code !== 0) {
|
||||
let msg = '' + code;
|
||||
if (messages.length)
|
||||
msg += ': ' + messages.join();
|
||||
return next(new ldap.OperationsError(msg));
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
server.modify('ou=users, o=myhost', pre, (req, res, next) => {
|
||||
if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn])
|
||||
return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
if (!req.changes.length)
|
||||
return next(new ldap.ProtocolError('changes required'));
|
||||
|
||||
const user = req.users[req.dn.rdns[0].cn].attributes;
|
||||
let mod;
|
||||
|
||||
for (const change of req.changes) {
|
||||
mod = change.modification;
|
||||
switch (change.operation) {
|
||||
case 'replace':
|
||||
if (mod.type !== 'userpassword' || !mod.vals || !mod.vals.length)
|
||||
return next(new ldap.UnwillingToPerformError('only password updates ' +
|
||||
'allowed'));
|
||||
break;
|
||||
case 'add':
|
||||
case 'delete':
|
||||
return next(new ldap.UnwillingToPerformError('only replace allowed'));
|
||||
}
|
||||
}
|
||||
|
||||
const passwd = spawn('chpasswd', ['-c', 'MD5']);
|
||||
passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8');
|
||||
|
||||
passwd.on('exit', (code) => {
|
||||
if (code !== 0)
|
||||
return next(new ldap.OperationsError('' + code));
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
server.del('ou=users, o=myhost', pre, (req, res, next) => {
|
||||
if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn])
|
||||
return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
const userdel = spawn('userdel', ['-f', req.dn.rdns[0].cn]);
|
||||
|
||||
const messages = [];
|
||||
userdel.stdout.on('data', (data) => {
|
||||
messages.push(data.toString());
|
||||
});
|
||||
userdel.stderr.on('data', (data) => {
|
||||
messages.push(data.toString());
|
||||
});
|
||||
|
||||
userdel.on('exit', (code) => {
|
||||
if (code !== 0) {
|
||||
let msg = '' + code;
|
||||
if (messages.length)
|
||||
msg += ': ' + messages.join();
|
||||
return next(new ldap.OperationsError(msg));
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
server.search('o=myhost', pre, (req, res, next) => {
|
||||
const keys = Object.keys(req.users);
|
||||
for (const k of keys) {
|
||||
if (req.filter.matches(req.users[k].attributes))
|
||||
res.send(req.users[k]);
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
|
||||
|
||||
// LDAP "standard" listens on 389, but whatever.
|
||||
server.listen(1389, '127.0.0.1', () => {
|
||||
console.log('/etc/passwd LDAP server up at: %s', server.url);
|
||||
});
|
||||
```
|
||||
|
||||
# Address Book
|
||||
|
||||
This example is courtesy of [Diogo Resende](https://github.com/dresende) and
|
||||
illustrates setting up an address book for typical mail clients such as
|
||||
Thunderbird or Evolution over a MySQL database.
|
||||
|
||||
```js
|
||||
// MySQL test: (create on database 'abook' with username 'abook' and password 'abook')
|
||||
//
|
||||
// CREATE TABLE IF NOT EXISTS `users` (
|
||||
// `id` int(5) unsigned NOT NULL AUTO_INCREMENT,
|
||||
// `username` varchar(50) NOT NULL,
|
||||
// `password` varchar(50) NOT NULL,
|
||||
// PRIMARY KEY (`id`),
|
||||
// KEY `username` (`username`)
|
||||
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
// INSERT INTO `users` (`username`, `password`) VALUES
|
||||
// ('demo', 'demo');
|
||||
// CREATE TABLE IF NOT EXISTS `contacts` (
|
||||
// `id` int(5) unsigned NOT NULL AUTO_INCREMENT,
|
||||
// `user_id` int(5) unsigned NOT NULL,
|
||||
// `name` varchar(100) NOT NULL,
|
||||
// `email` varchar(255) NOT NULL,
|
||||
// PRIMARY KEY (`id`),
|
||||
// KEY `user_id` (`user_id`)
|
||||
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
// INSERT INTO `contacts` (`user_id`, `name`, `email`) VALUES
|
||||
// (1, 'John Doe', 'john.doe@example.com'),
|
||||
// (1, 'Jane Doe', 'jane.doe@example.com');
|
||||
//
|
||||
|
||||
const ldap = require('ldapjs');
|
||||
const mysql = require("mysql");
|
||||
const server = ldap.createServer();
|
||||
const addrbooks = {};
|
||||
const userinfo = {};
|
||||
const ldap_port = 389;
|
||||
const basedn = "dc=example, dc=com";
|
||||
const company = "Example";
|
||||
const db = mysql.createClient({
|
||||
user: "abook",
|
||||
password: "abook",
|
||||
database: "abook"
|
||||
});
|
||||
|
||||
db.query("SELECT c.*,u.username,u.password " +
|
||||
"FROM contacts c JOIN users u ON c.user_id=u.id",
|
||||
(err, contacts) => {
|
||||
if (err) {
|
||||
console.log("Error fetching contacts", err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
for (const contact of contacts) {
|
||||
if (!addrbooks.hasOwnProperty(contact.username)) {
|
||||
addrbooks[contact.username] = [];
|
||||
userinfo["cn=" + contact.username + ", " + basedn] = {
|
||||
abook: addrbooks[contact.username],
|
||||
pwd: contact.password
|
||||
};
|
||||
}
|
||||
|
||||
const p = contact.name.indexOf(" ");
|
||||
if (p != -1)
|
||||
contact.firstname = contact.name.substr(0, p);
|
||||
|
||||
p = contact.name.lastIndexOf(" ");
|
||||
if (p != -1)
|
||||
contact.surname = contact.name.substr(p + 1);
|
||||
|
||||
addrbooks[contact.username].push({
|
||||
dn: "cn=" + contact.name + ", " + basedn,
|
||||
attributes: {
|
||||
objectclass: [ "top" ],
|
||||
cn: contact.name,
|
||||
mail: contact.email,
|
||||
givenname: contact.firstname,
|
||||
sn: contact.surname,
|
||||
ou: company
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
server.bind(basedn, (req, res, next) => {
|
||||
const username = req.dn.toString();
|
||||
const password = req.credentials;
|
||||
|
||||
if (!userinfo.hasOwnProperty(username) ||
|
||||
userinfo[username].pwd != password) {
|
||||
return next(new ldap.InvalidCredentialsError());
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
server.search(basedn, (req, res, next) => {
|
||||
const binddn = req.connection.ldap.bindDN.toString();
|
||||
|
||||
if (userinfo.hasOwnProperty(binddn)) {
|
||||
for (const abook of userinfo[binddn].abook) {
|
||||
if (req.filter.matches(abook.attributes))
|
||||
res.send(abook);
|
||||
}
|
||||
}
|
||||
res.end();
|
||||
});
|
||||
|
||||
server.listen(ldap_port, () => {
|
||||
console.log("Addressbook started at %s", server.url);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
To test out this example, try:
|
||||
|
||||
```shell
|
||||
$ ldapsearch -H ldap://localhost:389 -x -D cn=demo,dc=example,dc=com \
|
||||
-w demo -b "dc=example,dc=com" objectclass=*
|
||||
```
|
||||
|
||||
# Multi-threaded Server
|
||||
|
||||
This example demonstrates multi-threading via the `cluster` module utilizing a `net` server for initial socket receipt. An alternate example demonstrating use of the `connectionRouter` `serverOptions` hook is available in the `examples` directory.
|
||||
|
||||
```js
|
||||
const cluster = require('cluster');
|
||||
const ldap = require('ldapjs');
|
||||
const net = require('net');
|
||||
const os = require('os');
|
||||
|
||||
const threads = [];
|
||||
threads.getNext = function () {
|
||||
return (Math.floor(Math.random() * this.length));
|
||||
};
|
||||
|
||||
const serverOptions = {
|
||||
port: 1389
|
||||
};
|
||||
|
||||
if (cluster.isMaster) {
|
||||
const server = net.createServer(serverOptions, (socket) => {
|
||||
socket.pause();
|
||||
console.log('ldapjs client requesting connection');
|
||||
let routeTo = threads.getNext();
|
||||
threads[routeTo].send({ type: 'connection' }, socket);
|
||||
});
|
||||
|
||||
for (let i = 0; i < os.cpus().length; i++) {
|
||||
let thread = cluster.fork({
|
||||
'id': i
|
||||
});
|
||||
thread.id = i;
|
||||
thread.on('message', function (msg) {
|
||||
|
||||
});
|
||||
threads.push(thread);
|
||||
}
|
||||
|
||||
server.listen(serverOptions.port, function () {
|
||||
console.log('ldapjs listening at ldap://127.0.0.1:' + serverOptions.port);
|
||||
});
|
||||
} else {
|
||||
const server = ldap.createServer(serverOptions);
|
||||
|
||||
let threadId = process.env.id;
|
||||
|
||||
process.on('message', (msg, socket) => {
|
||||
switch (msg.type) {
|
||||
case 'connection':
|
||||
server.newConnection(socket);
|
||||
socket.resume();
|
||||
console.log('ldapjs client connection accepted on ' + threadId.toString());
|
||||
}
|
||||
});
|
||||
|
||||
server.search('dc=example', function (req, res, next) {
|
||||
console.log('ldapjs search initiated on ' + threadId.toString());
|
||||
var obj = {
|
||||
dn: req.dn.toString(),
|
||||
attributes: {
|
||||
objectclass: ['organization', 'top'],
|
||||
o: 'example'
|
||||
}
|
||||
};
|
||||
|
||||
if (req.filter.matches(obj.attributes))
|
||||
res.send(obj);
|
||||
|
||||
res.end();
|
||||
});
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user