docs: update code

This commit is contained in:
Tony Brix 2021-02-24 16:03:35 -06:00
parent 2ee04b14a3
commit e4b72cde9b
8 changed files with 1272 additions and 1090 deletions

View File

@ -15,14 +15,17 @@ with LDAP. If you're not, read the [guide](guide.html) first.
The code to create a new client looks like: The code to create a new client looks like:
var ldap = require('ldapjs'); ```js
var client = ldap.createClient({ const ldap = require('ldapjs');
url: ['ldap://127.0.0.1:1389', 'ldap://127.0.0.2:1389']
});
client.on('error', (err) => { const client = ldap.createClient({
url: ['ldap://127.0.0.1:1389', 'ldap://127.0.0.2:1389']
});
client.on('error', (err) => {
// handle connection error // handle connection error
}) })
```
You can use `ldap://` or `ldaps://`; the latter would connect over SSL (note You can use `ldap://` or `ldaps://`; the latter would connect over SSL (note
that this will not use the LDAP TLS extended operation, but literally an SSL that this will not use the LDAP TLS extended operation, but literally an SSL
@ -104,9 +107,6 @@ Almost every operation has the callback form of `function(err, res)` where err
will be an instance of an `LDAPError` (you can use `instanceof` to switch). will be an instance of an `LDAPError` (you can use `instanceof` to switch).
You probably won't need to check the `res` parameter, but it's there if you do. You probably won't need to check the `res` parameter, but it's there if you do.
# bind # bind
`bind(dn, password, controls, callback)` `bind(dn, password, controls, callback)`
@ -118,9 +118,11 @@ of `Control` objects. You probably don't need them though...
Example: Example:
client.bind('cn=root', 'secret', function(err) { ```js
client.bind('cn=root', 'secret', (err) => {
assert.ifError(err); assert.ifError(err);
}); });
```
# add # add
`add(dn, entry, controls, callback)` `add(dn, entry, controls, callback)`
@ -132,15 +134,17 @@ controls are optional.
Example: Example:
var entry = { ```js
const entry = {
cn: 'foo', cn: 'foo',
sn: 'bar', sn: 'bar',
email: ['foo@bar.com', 'foo1@bar.com'], email: ['foo@bar.com', 'foo1@bar.com'],
objectclass: 'fooPerson' objectclass: 'fooPerson'
}; };
client.add('cn=foo, o=example', entry, function(err) { client.add('cn=foo, o=example', entry, (err) => {
assert.ifError(err); assert.ifError(err);
}); });
```
# compare # compare
`compare(dn, attribute, value, controls, callback)` `compare(dn, attribute, value, controls, callback)`
@ -150,11 +154,13 @@ the entry referenced by dn.
Example: Example:
client.compare('cn=foo, o=example', 'sn', 'bar', function(err, matched) { ```js
client.compare('cn=foo, o=example', 'sn', 'bar', (err, matched) => {
assert.ifError(err); assert.ifError(err);
console.log('matched: ' + matched); console.log('matched: ' + matched);
}); });
```
# del # del
`del(dn, controls, callback)` `del(dn, controls, callback)`
@ -164,9 +170,11 @@ Deletes an entry from the LDAP server.
Example: Example:
client.del('cn=foo, o=example', function(err) { ```js
client.del('cn=foo, o=example', (err) => {
assert.ifError(err); assert.ifError(err);
}); });
```
# exop # exop
`exop(name, value, controls, callback)` `exop(name, value, controls, callback)`
@ -178,11 +186,13 @@ should be.
Example (performs an LDAP 'whois' extended op): Example (performs an LDAP 'whois' extended op):
client.exop('1.3.6.1.4.1.4203.1.11.3', function(err, value, res) { ```js
client.exop('1.3.6.1.4.1.4203.1.11.3', (err, value, res) => {
assert.ifError(err); assert.ifError(err);
console.log('whois: ' + value); console.log('whois: ' + value);
}); });
```
# modify # modify
`modify(name, changes, controls, callback)` `modify(name, changes, controls, callback)`
@ -193,16 +203,18 @@ pass in a single `Change` or an array of `Change` objects.
Example: Example:
var change = new ldap.Change({ ```js
const change = new ldap.Change({
operation: 'add', operation: 'add',
modification: { modification: {
pets: ['cat', 'dog'] pets: ['cat', 'dog']
} }
}); });
client.modify('cn=foo, o=example', change, function(err) { client.modify('cn=foo, o=example', change, (err) => {
assert.ifError(err); assert.ifError(err);
}); });
```
## Change ## Change
@ -232,9 +244,11 @@ as opposed to just renaming the leaf).
Example: Example:
client.modifyDN('cn=foo, o=example', 'cn=bar', function(err) { ```js
client.modifyDN('cn=foo, o=example', 'cn=bar', (err) => {
assert.ifError(err); assert.ifError(err);
}); });
```
# search # search
`search(base, options, controls, callback)` `search(base, options, controls, callback)`
@ -274,28 +288,30 @@ the code matching.
Example: Example:
var opts = { ```js
const opts = {
filter: '(&(l=Seattle)(email=*@foo.com))', filter: '(&(l=Seattle)(email=*@foo.com))',
scope: 'sub', scope: 'sub',
attributes: ['dn', 'sn', 'cn'] attributes: ['dn', 'sn', 'cn']
}; };
client.search('o=example', opts, function(err, res) { client.search('o=example', opts, (err, res) => {
assert.ifError(err); assert.ifError(err);
res.on('searchEntry', function(entry) { res.on('searchEntry', (entry) => {
console.log('entry: ' + JSON.stringify(entry.object)); console.log('entry: ' + JSON.stringify(entry.object));
}); });
res.on('searchReference', function(referral) { res.on('searchReference', (referral) => {
console.log('referral: ' + referral.uris.join()); console.log('referral: ' + referral.uris.join());
}); });
res.on('error', function(err) { res.on('error', (err) => {
console.error('error: ' + err.message); console.error('error: ' + err.message);
}); });
res.on('end', function(result) { res.on('end', (result) => {
console.log('status: ' + result.status); console.log('status: ' + result.status);
}); });
}); });
```
## Filter Strings ## Filter Strings
@ -310,14 +326,18 @@ in prefix notation. For example, let's start simple, and build up a complicated
filter. The most basic filter is equality, so let's assume you want to search filter. The most basic filter is equality, so let's assume you want to search
for an attribute `email` with a value of `foo@bar.com`. The syntax would be: for an attribute `email` with a value of `foo@bar.com`. The syntax would be:
(email=foo@bar.com) ```
(email=foo@bar.com)
```
ldapjs requires all filters to be surrounded by '()' blocks. Ok, that was easy. ldapjs requires all filters to be surrounded by '()' blocks. Ok, that was easy.
Let's now assume that you want to find all records where the email is actually Let's now assume that you want to find all records where the email is actually
just anything in the "@bar.com" domain and the location attribute is set to just anything in the "@bar.com" domain and the location attribute is set to
Seattle: Seattle:
(&(email=*@bar.com)(l=Seattle)) ```
(&(email=*@bar.com)(l=Seattle))
```
Now our filter is actually three LDAP filters. We have an `and` filter (single Now our filter is actually three LDAP filters. We have an `and` filter (single
amp `&`), an `equality` filter `(the l=Seattle)`, and a `substring` filter. amp `&`), an `equality` filter `(the l=Seattle)`, and a `substring` filter.
@ -328,7 +348,9 @@ to match any email of @bar.com or its subdomains like `"example@foo.bar.com"`.
Now, let's say we also want to set our filter to include a Now, let's say we also want to set our filter to include a
specification that either the employeeType *not* be a manager nor a secretary: specification that either the employeeType *not* be a manager nor a secretary:
(&(email=*@bar.com)(l=Seattle)(!(|(employeeType=manager)(employeeType=secretary)))) ```
(&(email=*@bar.com)(l=Seattle)(!(|(employeeType=manager)(employeeType=secretary))))
```
The `not` character is represented as a `!`, the `or` as a single pipe `|`. The `not` character is represented as a `!`, the `or` as a single pipe `|`.
It gets a little bit complicated, but it's actually quite powerful, and lets you It gets a little bit complicated, but it's actually quite powerful, and lets you
@ -343,27 +365,29 @@ While callers could choose to do this manually via the `controls` parameter to
most simple way to use the paging automation is to set the `paged` option to most simple way to use the paging automation is to set the `paged` option to
true when performing a search: true when performing a search:
var opts = { ```js
const opts = {
filter: '(objectclass=commonobject)', filter: '(objectclass=commonobject)',
scope: 'sub', scope: 'sub',
paged: true, paged: true,
sizeLimit: 200 sizeLimit: 200
}; };
client.search('o=largedir', opts, function(err, res) { client.search('o=largedir', opts, (err, res) => {
assert.ifError(err); assert.ifError(err);
res.on('searchEntry', function(entry) { res.on('searchEntry', (entry) => {
// do per-entry processing // do per-entry processing
}); });
res.on('page', function(result) { res.on('page', (result) => {
console.log('page end'); console.log('page end');
}); });
res.on('error', function(resErr) { res.on('error', (resErr) => {
assert.ifError(resErr); assert.ifError(resErr);
}); });
res.on('end', function(result) { res.on('end', (result) => {
console.log('done '); console.log('done ');
}); });
}); });
```
This will enable paging with a default page size of 199 (`sizeLimit` - 1) and This will enable paging with a default page size of 199 (`sizeLimit` - 1) and
will output all of the resulting objects via the `searchEntry` event. At the will output all of the resulting objects via the `searchEntry` event. At the
@ -381,32 +405,34 @@ client will wait to request the next page until that callback is executed.
Here is an example where both of those parameters are used: Here is an example where both of those parameters are used:
var queue = new MyWorkQueue(someSlowWorkFunction); ```js
var opts = { const queue = new MyWorkQueue(someSlowWorkFunction);
const opts = {
filter: '(objectclass=commonobject)', filter: '(objectclass=commonobject)',
scope: 'sub', scope: 'sub',
paged: { paged: {
pageSize: 250, pageSize: 250,
pagePause: true pagePause: true
}, },
}; };
client.search('o=largerdir', opts, function(err, res) { client.search('o=largerdir', opts, (err, res) => {
assert.ifError(err); assert.ifError(err);
res.on('searchEntry', function(entry) { res.on('searchEntry', (entry) => {
// Submit incoming objects to queue // Submit incoming objects to queue
queue.push(entry); queue.push(entry);
}); });
res.on('page', function(result, cb) { res.on('page', (result, cb) => {
// Allow the queue to flush before fetching next page // Allow the queue to flush before fetching next page
queue.cbWhenFlushed(cb); queue.cbWhenFlushed(cb);
}); });
res.on('error', function(resErr) { res.on('error', (resErr) => {
assert.ifError(resErr); assert.ifError(resErr);
}); });
res.on('end', function(result) { res.on('end', (result) => {
console.log('done'); console.log('done');
}); });
}); });
```
# starttls # starttls
`starttls(options, controls, callback)` `starttls(options, controls, callback)`
@ -415,15 +441,17 @@ Attempt to secure existing LDAP connection via STARTTLS.
Example: Example:
var opts = { ```js
const opts = {
ca: [fs.readFileSync('mycacert.pem')] ca: [fs.readFileSync('mycacert.pem')]
}; };
client.starttls(opts, function(err, res) { client.starttls(opts, (err, res) => {
assert.ifError(err); assert.ifError(err);
// Client communication now TLS protected // Client communication now TLS protected
}); });
```
# unbind # unbind
@ -440,6 +468,8 @@ not have a response.
Example: Example:
client.unbind(function(err) { ```js
client.unbind((err) => {
assert.ifError(err); assert.ifError(err);
}); });
```

View File

@ -26,10 +26,12 @@ The `parseDN` API converts a string representation of a DN into an ldapjs DN
object; in most cases this will be handled for you under the covers of the object; in most cases this will be handled for you under the covers of the
ldapjs framework, but if you need it, it's there. ldapjs framework, but if you need it, it's there.
var parseDN = require('ldapjs').parseDN; ```js
const parseDN = require('ldapjs').parseDN;
var dn = parseDN('cn=foo+sn=bar, ou=people, o=example'); const dn = parseDN('cn=foo+sn=bar, ou=people, o=example');
console.log(dn.toString()); console.log(dn.toString());
```
# DN # DN
@ -41,40 +43,46 @@ APIs are setup to give you a DN object.
Returns a boolean indicating whether 'this' is a child of the passed in dn. The Returns a boolean indicating whether 'this' is a child of the passed in dn. The
`dn` argument can be either a string or a DN. `dn` argument can be either a string or a DN.
server.add('o=example', function(req, res, next) { ```js
server.add('o=example', (req, res, next) => {
if (req.dn.childOf('ou=people, o=example')) { if (req.dn.childOf('ou=people, o=example')) {
... ...
} else { } else {
... ...
} }
}); });
```
## parentOf(dn) ## parentOf(dn)
The inverse of `childOf`; returns a boolean on whether or not `this` is a parent The inverse of `childOf`; returns a boolean on whether or not `this` is a parent
of the passed in dn. Like `childOf`, can take either a string or a DN. of the passed in dn. Like `childOf`, can take either a string or a DN.
server.add('o=example', function(req, res, next) { ```js
var dn = parseDN('ou=people, o=example'); server.add('o=example', (req, res, next) => {
const dn = parseDN('ou=people, o=example');
if (dn.parentOf(req.dn)) { if (dn.parentOf(req.dn)) {
... ...
} else { } else {
... ...
} }
}); });
```
## equals(dn) ## equals(dn)
Returns a boolean indicating whether `this` is equivalent to the passed in `dn` Returns a boolean indicating whether `this` is equivalent to the passed in `dn`
argument. `dn` can be a string or a DN. argument. `dn` can be a string or a DN.
server.add('o=example', function(req, res, next) { ```js
server.add('o=example', (req, res, next) => {
if (req.dn.equals('cn=foo, ou=people, o=example')) { if (req.dn.equals('cn=foo, ou=people, o=example')) {
... ...
} else { } else {
... ...
} }
}); });
```
## parent() ## parent()
@ -112,6 +120,8 @@ It accepts the same parameters as `format`.
Returns the string representation of `this`. Returns the string representation of `this`.
server.add('o=example', function(req, res, next) { ```js
server.add('o=example', (req, res, next) => {
console.log(req.dn.toString()); console.log(req.dn.toString());
}); });
```

View File

@ -17,12 +17,13 @@ a `stack` property correctly set.
In general, you'll be using the errors in ldapjs like: In general, you'll be using the errors in ldapjs like:
var ldap = require('ldapjs'); ```js
const ldap = require('ldapjs');
var db = {}; const db = {};
server.add('o=example', function(req, res, next) { server.add('o=example', (req, res, next) => {
var parent = req.dn.parent(); const parent = req.dn.parent();
if (parent) { if (parent) {
if (!db[parent.toString()]) if (!db[parent.toString()])
return next(new ldap.NoSuchObjectError(parent.toString())); return next(new ldap.NoSuchObjectError(parent.toString()));
@ -31,7 +32,8 @@ In general, you'll be using the errors in ldapjs like:
return next(new ldap.EntryAlreadyExistsError(req.dn.toString())); return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
... ...
}); });
```
I.e., if you just pass them into the `next()` handler, ldapjs will automatically I.e., if you just pass them into the `next()` handler, ldapjs will automatically
return the appropriate LDAP error message, and stop the handler chain. return the appropriate LDAP error message, and stop the handler chain.

View File

@ -13,39 +13,40 @@ with ldapjs.
# In-memory server # In-memory server
var ldap = require('ldapjs'); ```js
const ldap = require('ldapjs');
///--- Shared handlers ///--- Shared handlers
function authorize(req, res, next) { function authorize(req, res, next) {
/* Any user may search after bind, only cn=root has full power */ /* Any user may search after bind, only cn=root has full power */
var isSearch = (req instanceof ldap.SearchRequest); const isSearch = (req instanceof ldap.SearchRequest);
if (!req.connection.ldap.bindDN.equals('cn=root') && !isSearch) if (!req.connection.ldap.bindDN.equals('cn=root') && !isSearch)
return next(new ldap.InsufficientAccessRightsError()); return next(new ldap.InsufficientAccessRightsError());
return next(); return next();
} }
///--- Globals ///--- Globals
var SUFFIX = 'o=joyent'; const SUFFIX = 'o=joyent';
var db = {}; const db = {};
var server = ldap.createServer(); const server = ldap.createServer();
server.bind('cn=root', function(req, res, next) { server.bind('cn=root', (req, res, next) => {
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret') if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
return next(new ldap.InvalidCredentialsError()); return next(new ldap.InvalidCredentialsError());
res.end(); res.end();
return next(); return next();
}); });
server.add(SUFFIX, authorize, function(req, res, next) { server.add(SUFFIX, authorize, (req, res, next) => {
var dn = req.dn.toString(); const dn = req.dn.toString();
if (db[dn]) if (db[dn])
return next(new ldap.EntryAlreadyExistsError(dn)); return next(new ldap.EntryAlreadyExistsError(dn));
@ -53,10 +54,10 @@ with ldapjs.
db[dn] = req.toObject().attributes; db[dn] = req.toObject().attributes;
res.end(); res.end();
return next(); return next();
}); });
server.bind(SUFFIX, function(req, res, next) { server.bind(SUFFIX, (req, res, next) => {
var dn = req.dn.toString(); const dn = req.dn.toString();
if (!db[dn]) if (!db[dn])
return next(new ldap.NoSuchObjectError(dn)); return next(new ldap.NoSuchObjectError(dn));
@ -68,20 +69,20 @@ with ldapjs.
res.end(); res.end();
return next(); return next();
}); });
server.compare(SUFFIX, authorize, function(req, res, next) { server.compare(SUFFIX, authorize, (req, res, next) => {
var dn = req.dn.toString(); const dn = req.dn.toString();
if (!db[dn]) if (!db[dn])
return next(new ldap.NoSuchObjectError(dn)); return next(new ldap.NoSuchObjectError(dn));
if (!db[dn][req.attribute]) if (!db[dn][req.attribute])
return next(new ldap.NoSuchAttributeError(req.attribute)); return next(new ldap.NoSuchAttributeError(req.attribute));
var matches = false; const matches = false;
var vals = db[dn][req.attribute]; const vals = db[dn][req.attribute];
for (var i = 0; i < vals.length; i++) { for (const value of vals) {
if (vals[i] === req.value) { if (value === req.value) {
matches = true; matches = true;
break; break;
} }
@ -89,10 +90,10 @@ with ldapjs.
res.end(matches); res.end(matches);
return next(); return next();
}); });
server.del(SUFFIX, authorize, function(req, res, next) { server.del(SUFFIX, authorize, (req, res, next) => {
var dn = req.dn.toString(); const dn = req.dn.toString();
if (!db[dn]) if (!db[dn])
return next(new ldap.NoSuchObjectError(dn)); return next(new ldap.NoSuchObjectError(dn));
@ -100,20 +101,20 @@ with ldapjs.
res.end(); res.end();
return next(); return next();
}); });
server.modify(SUFFIX, authorize, function(req, res, next) { server.modify(SUFFIX, authorize, (req, res, next) => {
var dn = req.dn.toString(); const dn = req.dn.toString();
if (!req.changes.length) if (!req.changes.length)
return next(new ldap.ProtocolError('changes required')); return next(new ldap.ProtocolError('changes required'));
if (!db[dn]) if (!db[dn])
return next(new ldap.NoSuchObjectError(dn)); return next(new ldap.NoSuchObjectError(dn));
var entry = db[dn]; const entry = db[dn];
for (var i = 0; i < req.changes.length; i++) { for (const change of req.changes) {
mod = req.changes[i].modification; mod = change.modification;
switch (req.changes[i].operation) { switch (change.operation) {
case 'replace': case 'replace':
if (!entry[mod.type]) if (!entry[mod.type])
return next(new ldap.NoSuchAttributeError(mod.type)); return next(new ldap.NoSuchAttributeError(mod.type));
@ -130,10 +131,10 @@ with ldapjs.
if (!entry[mod.type]) { if (!entry[mod.type]) {
entry[mod.type] = mod.vals; entry[mod.type] = mod.vals;
} else { } else {
mod.vals.forEach(function(v) { for (const v of mod.vals) {
if (entry[mod.type].indexOf(v) === -1) if (entry[mod.type].indexOf(v) === -1)
entry[mod.type].push(v); entry[mod.type].push(v);
}); }
} }
break; break;
@ -150,14 +151,14 @@ with ldapjs.
res.end(); res.end();
return next(); return next();
}); });
server.search(SUFFIX, authorize, function(req, res, next) { server.search(SUFFIX, authorize, (req, res, next) => {
var dn = req.dn.toString(); const dn = req.dn.toString();
if (!db[dn]) if (!db[dn])
return next(new ldap.NoSuchObjectError(dn)); return next(new ldap.NoSuchObjectError(dn));
var scopeCheck; let scopeCheck;
switch (req.scope) { switch (req.scope) {
case 'base': case 'base':
@ -172,24 +173,25 @@ with ldapjs.
return next(); return next();
case 'one': case 'one':
scopeCheck = function(k) { scopeCheck = (k) => {
if (req.dn.equals(k)) if (req.dn.equals(k))
return true; return true;
var parent = ldap.parseDN(k).parent(); const parent = ldap.parseDN(k).parent();
return (parent ? parent.equals(req.dn) : false); return (parent ? parent.equals(req.dn) : false);
}; };
break; break;
case 'sub': case 'sub':
scopeCheck = function(k) { scopeCheck = (k) => {
return (req.dn.equals(k) || req.dn.parentOf(k)); return (req.dn.equals(k) || req.dn.parentOf(k));
}; };
break; break;
} }
Object.keys(db).forEach(function(key) { const keys = Object.keys(db);
for (const key of keys) {
if (!scopeCheck(key)) if (!scopeCheck(key))
return; return;
@ -199,51 +201,53 @@ with ldapjs.
attributes: db[key] attributes: db[key]
}); });
} }
}); }
res.end(); res.end();
return next(); return next();
}); });
///--- Fire it up ///--- Fire it up
server.listen(1389, function() { server.listen(1389, () => {
console.log('LDAP server up at: %s', server.url); console.log('LDAP server up at: %s', server.url);
}); });
```
# /etc/passwd server # /etc/passwd server
var fs = require('fs'); ```js
var ldap = require('ldapjs'); const fs = require('fs');
var spawn = require('child_process').spawn; const ldap = require('ldapjs');
const { spawn } = require('child_process');
///--- Shared handlers ///--- Shared handlers
function authorize(req, res, next) { function authorize(req, res, next) {
if (!req.connection.ldap.bindDN.equals('cn=root')) if (!req.connection.ldap.bindDN.equals('cn=root'))
return next(new ldap.InsufficientAccessRightsError()); return next(new ldap.InsufficientAccessRightsError());
return next(); return next();
} }
function loadPasswdFile(req, res, next) { function loadPasswdFile(req, res, next) {
fs.readFile('/etc/passwd', 'utf8', function(err, data) { fs.readFile('/etc/passwd', 'utf8', (err, data) => {
if (err) if (err)
return next(new ldap.OperationsError(err.message)); return next(new ldap.OperationsError(err.message));
req.users = {}; req.users = {};
var lines = data.split('\n'); const lines = data.split('\n');
for (var i = 0; i < lines.length; i++) { for (const line of lines) {
if (!lines[i] || /^#/.test(lines[i])) if (!line || /^#/.test(line))
continue; continue;
var record = lines[i].split(':'); const record = line.split(':');
if (!record || !record.length) if (!record || !record.length)
continue; continue;
@ -263,39 +267,39 @@ with ldapjs.
return next(); return next();
}); });
} }
var pre = [authorize, loadPasswdFile]; const pre = [authorize, loadPasswdFile];
///--- Mainline ///--- Mainline
var server = ldap.createServer(); const server = ldap.createServer();
server.bind('cn=root', function(req, res, next) { server.bind('cn=root', (req, res, next) => {
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret') if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
return next(new ldap.InvalidCredentialsError()); return next(new ldap.InvalidCredentialsError());
res.end(); res.end();
return next(); return next();
}); });
server.add('ou=users, o=myhost', pre, function(req, res, next) { server.add('ou=users, o=myhost', pre, (req, res, next) => {
if (!req.dn.rdns[0].cn) if (!req.dn.rdns[0].cn)
return next(new ldap.ConstraintViolationError('cn required')); return next(new ldap.ConstraintViolationError('cn required'));
if (req.users[req.dn.rdns[0].cn]) if (req.users[req.dn.rdns[0].cn])
return next(new ldap.EntryAlreadyExistsError(req.dn.toString())); return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
var entry = req.toObject().attributes; const entry = req.toObject().attributes;
if (entry.objectclass.indexOf('unixUser') === -1) if (entry.objectclass.indexOf('unixUser') === -1)
return next(new ldap.ConstraintViolationError('entry must be a unixUser')); return next(new ldap.ConstraintViolationError('entry must be a unixUser'));
var opts = ['-m']; const opts = ['-m'];
if (entry.description) { if (entry.description) {
opts.push('-c'); opts.push('-c');
opts.push(entry.description[0]); opts.push(entry.description[0]);
@ -317,20 +321,20 @@ with ldapjs.
opts.push(entry.uid[0]); opts.push(entry.uid[0]);
} }
opts.push(entry.cn[0]); opts.push(entry.cn[0]);
var useradd = spawn('useradd', opts); const useradd = spawn('useradd', opts);
var messages = []; const messages = [];
useradd.stdout.on('data', function(data) { useradd.stdout.on('data', (data) => {
messages.push(data.toString()); messages.push(data.toString());
}); });
useradd.stderr.on('data', function(data) { useradd.stderr.on('data', (data) => {
messages.push(data.toString()); messages.push(data.toString());
}); });
useradd.on('exit', function(code) { useradd.on('exit', (code) => {
if (code !== 0) { if (code !== 0) {
var msg = '' + code; let msg = '' + code;
if (messages.length) if (messages.length)
msg += ': ' + messages.join(); msg += ': ' + messages.join();
return next(new ldap.OperationsError(msg)); return next(new ldap.OperationsError(msg));
@ -339,22 +343,22 @@ with ldapjs.
res.end(); res.end();
return next(); return next();
}); });
}); });
server.modify('ou=users, o=myhost', pre, function(req, res, next) { server.modify('ou=users, o=myhost', pre, (req, res, next) => {
if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn]) if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn])
return next(new ldap.NoSuchObjectError(req.dn.toString())); return next(new ldap.NoSuchObjectError(req.dn.toString()));
if (!req.changes.length) if (!req.changes.length)
return next(new ldap.ProtocolError('changes required')); return next(new ldap.ProtocolError('changes required'));
var user = req.users[req.dn.rdns[0].cn].attributes; const user = req.users[req.dn.rdns[0].cn].attributes;
var mod; let mod;
for (var i = 0; i < req.changes.length; i++) { for (const change of req.changes) {
mod = req.changes[i].modification; mod = change.modification;
switch (req.changes[i].operation) { switch (change.operation) {
case 'replace': case 'replace':
if (mod.type !== 'userpassword' || !mod.vals || !mod.vals.length) if (mod.type !== 'userpassword' || !mod.vals || !mod.vals.length)
return next(new ldap.UnwillingToPerformError('only password updates ' + return next(new ldap.UnwillingToPerformError('only password updates ' +
@ -366,36 +370,36 @@ with ldapjs.
} }
} }
var passwd = spawn('chpasswd', ['-c', 'MD5']); const passwd = spawn('chpasswd', ['-c', 'MD5']);
passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8'); passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8');
passwd.on('exit', function(code) { passwd.on('exit', (code) => {
if (code !== 0) if (code !== 0)
return next(new ldap.OperationsError('' + code)); return next(new ldap.OperationsError('' + code));
res.end(); res.end();
return next(); return next();
}); });
}); });
server.del('ou=users, o=myhost', pre, function(req, res, next) { server.del('ou=users, o=myhost', pre, (req, res, next) => {
if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn]) if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn])
return next(new ldap.NoSuchObjectError(req.dn.toString())); return next(new ldap.NoSuchObjectError(req.dn.toString()));
var userdel = spawn('userdel', ['-f', req.dn.rdns[0].cn]); const userdel = spawn('userdel', ['-f', req.dn.rdns[0].cn]);
var messages = []; const messages = [];
userdel.stdout.on('data', function(data) { userdel.stdout.on('data', (data) => {
messages.push(data.toString()); messages.push(data.toString());
}); });
userdel.stderr.on('data', function(data) { userdel.stderr.on('data', (data) => {
messages.push(data.toString()); messages.push(data.toString());
}); });
userdel.on('exit', function(code) { userdel.on('exit', (code) => {
if (code !== 0) { if (code !== 0) {
var msg = '' + code; let msg = '' + code;
if (messages.length) if (messages.length)
msg += ': ' + messages.join(); msg += ': ' + messages.join();
return next(new ldap.OperationsError(msg)); return next(new ldap.OperationsError(msg));
@ -404,25 +408,27 @@ with ldapjs.
res.end(); res.end();
return next(); return next();
}); });
}); });
server.search('o=myhost', pre, function(req, res, next) { server.search('o=myhost', pre, (req, res, next) => {
Object.keys(req.users).forEach(function(k) { const keys = Object.keys(req.users);
for (const k of keys) {
if (req.filter.matches(req.users[k].attributes)) if (req.filter.matches(req.users[k].attributes))
res.send(req.users[k]); res.send(req.users[k]);
}); }
res.end(); res.end();
return next(); return next();
}); });
// LDAP "standard" listens on 389, but whatever. // LDAP "standard" listens on 389, but whatever.
server.listen(1389, '127.0.0.1', function() { server.listen(1389, '127.0.0.1', () => {
console.log('/etc/passwd LDAP server up at: %s', server.url); console.log('/etc/passwd LDAP server up at: %s', server.url);
}); });
```
# Address Book # Address Book
@ -430,84 +436,86 @@ This example is courtesy of [Diogo Resende](https://github.com/dresende) and
illustrates setting up an address book for typical mail clients such as illustrates setting up an address book for typical mail clients such as
Thunderbird or Evolution over a MySQL database. Thunderbird or Evolution over a MySQL database.
// MySQL test: (create on database 'abook' with username 'abook' and password 'abook') ```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, // CREATE TABLE IF NOT EXISTS `users` (
// `username` varchar(50) NOT NULL, // `id` int(5) unsigned NOT NULL AUTO_INCREMENT,
// `password` varchar(50) NOT NULL, // `username` varchar(50) NOT NULL,
// PRIMARY KEY (`id`), // `password` varchar(50) NOT NULL,
// KEY `username` (`username`) // PRIMARY KEY (`id`),
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8; // KEY `username` (`username`)
// INSERT INTO `users` (`username`, `password`) VALUES // ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
// ('demo', 'demo'); // INSERT INTO `users` (`username`, `password`) VALUES
// CREATE TABLE IF NOT EXISTS `contacts` ( // ('demo', 'demo');
// `id` int(5) unsigned NOT NULL AUTO_INCREMENT, // CREATE TABLE IF NOT EXISTS `contacts` (
// `user_id` int(5) unsigned NOT NULL, // `id` int(5) unsigned NOT NULL AUTO_INCREMENT,
// `name` varchar(100) NOT NULL, // `user_id` int(5) unsigned NOT NULL,
// `email` varchar(255) NOT NULL, // `name` varchar(100) NOT NULL,
// PRIMARY KEY (`id`), // `email` varchar(255) NOT NULL,
// KEY `user_id` (`user_id`) // PRIMARY KEY (`id`),
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8; // KEY `user_id` (`user_id`)
// INSERT INTO `contacts` (`user_id`, `name`, `email`) VALUES // ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
// (1, 'John Doe', 'john.doe@example.com'), // INSERT INTO `contacts` (`user_id`, `name`, `email`) VALUES
// (1, 'Jane Doe', 'jane.doe@example.com'); // (1, 'John Doe', 'john.doe@example.com'),
// // (1, 'Jane Doe', 'jane.doe@example.com');
//
var ldap = require('ldapjs'), const ldap = require('ldapjs');
mysql = require("mysql"), const mysql = require("mysql");
server = ldap.createServer(), const server = ldap.createServer();
addrbooks = {}, userinfo = {}, const addrbooks = {};
ldap_port = 389, const userinfo = {};
basedn = "dc=example, dc=com", const ldap_port = 389;
company = "Example", const basedn = "dc=example, dc=com";
db = mysql.createClient({ const company = "Example";
const db = mysql.createClient({
user: "abook", user: "abook",
password: "abook", password: "abook",
database: "abook" database: "abook"
}); });
db.query("SELECT c.*,u.username,u.password " + db.query("SELECT c.*,u.username,u.password " +
"FROM contacts c JOIN users u ON c.user_id=u.id", "FROM contacts c JOIN users u ON c.user_id=u.id",
function(err, contacts) { (err, contacts) => {
if (err) { if (err) {
console.log("Error fetching contacts", err); console.log("Error fetching contacts", err);
process.exit(1); process.exit(1);
} }
for (var i = 0; i < contacts.length; i++) { for (const contact of contacts) {
if (!addrbooks.hasOwnProperty(contacts[i].username)) { if (!addrbooks.hasOwnProperty(contact.username)) {
addrbooks[contacts[i].username] = []; addrbooks[contact.username] = [];
userinfo["cn=" + contacts[i].username + ", " + basedn] = { userinfo["cn=" + contact.username + ", " + basedn] = {
abook: addrbooks[contacts[i].username], abook: addrbooks[contact.username],
pwd: contacts[i].password pwd: contact.password
}; };
} }
var p = contacts[i].name.indexOf(" "); const p = contact.name.indexOf(" ");
if (p != -1) if (p != -1)
contacts[i].firstname = contacts[i].name.substr(0, p); contact.firstname = contact.name.substr(0, p);
p = contacts[i].name.lastIndexOf(" "); p = contact.name.lastIndexOf(" ");
if (p != -1) if (p != -1)
contacts[i].surname = contacts[i].name.substr(p + 1); contact.surname = contact.name.substr(p + 1);
addrbooks[contacts[i].username].push({ addrbooks[contact.username].push({
dn: "cn=" + contacts[i].name + ", " + basedn, dn: "cn=" + contact.name + ", " + basedn,
attributes: { attributes: {
objectclass: [ "top" ], objectclass: [ "top" ],
cn: contacts[i].name, cn: contact.name,
mail: contacts[i].email, mail: contact.email,
givenname: contacts[i].firstname, givenname: contact.firstname,
sn: contacts[i].surname, sn: contact.surname,
ou: company ou: company
} }
}); });
} }
server.bind(basedn, function (req, res, next) { server.bind(basedn, (req, res, next) => {
var username = req.dn.toString(), const username = req.dn.toString();
password = req.credentials; const password = req.credentials;
if (!userinfo.hasOwnProperty(username) || if (!userinfo.hasOwnProperty(username) ||
userinfo[username].pwd != password) { userinfo[username].pwd != password) {
@ -518,24 +526,27 @@ Thunderbird or Evolution over a MySQL database.
return next(); return next();
}); });
server.search(basedn, function(req, res, next) { server.search(basedn, (req, res, next) => {
var binddn = req.connection.ldap.bindDN.toString(); const binddn = req.connection.ldap.bindDN.toString();
if (userinfo.hasOwnProperty(binddn)) { if (userinfo.hasOwnProperty(binddn)) {
for (var i = 0; i < userinfo[binddn].abook.length; i++) { for (const abook of userinfo[binddn].abook) {
if (req.filter.matches(userinfo[binddn].abook[i].attributes)) if (req.filter.matches(abook.attributes))
res.send(userinfo[binddn].abook[i]); res.send(abook);
} }
} }
res.end(); res.end();
}); });
server.listen(ldap_port, function() { server.listen(ldap_port, () => {
console.log("Addressbook started at %s", server.url); console.log("Addressbook started at %s", server.url);
}); });
}); });
```
To test out this example, try: To test out this example, try:
$ ldapsearch -H ldap://localhost:389 -x -D cn=demo,dc=example,dc=com \ ```sh
$ ldapsearch -H ldap://localhost:389 -x -D cn=demo,dc=example,dc=com \
-w demo -b "dc=example,dc=com" objectclass=* -w demo -b "dc=example,dc=com" objectclass=*
```

View File

@ -34,13 +34,17 @@ Parses an [RFC2254](http://www.ietf.org/rfc/rfc2254.txt) filter string into an
ldapjs object(s). If the filter is "complex", it will be a "tree" of objects. ldapjs object(s). If the filter is "complex", it will be a "tree" of objects.
For example: For example:
var parseFilter = require('ldapjs').parseFilter; ```js
const parseFilter = require('ldapjs').parseFilter;
var f = parseFilter('(objectclass=*)'); const f = parseFilter('(objectclass=*)');
```
Is a "simple" filter, and would just return a `PresenceFilter` object. However, Is a "simple" filter, and would just return a `PresenceFilter` object. However,
var f = parseFilter('(&(employeeType=manager)(l=Seattle))'); ```js
const f = parseFilter('(&(employeeType=manager)(l=Seattle))');
```
Would return an `AndFilter`, which would have a `filters` array of two Would return an `AndFilter`, which would have a `filters` array of two
`EqualityFilter` objects. `EqualityFilter` objects.
@ -59,13 +63,15 @@ The string syntax for an equality filter is `(attr=value)`.
The `matches()` method will return true IFF the passed in object has a The `matches()` method will return true IFF the passed in object has a
key matching `attribute` and a value matching `value`. key matching `attribute` and a value matching `value`.
var f = new EqualityFilter({ ```js
const f = new EqualityFilter({
attribute: 'cn', attribute: 'cn',
value: 'foo' value: 'foo'
}); });
f.matches({cn: 'foo'}); => true f.matches({cn: 'foo'}); => true
f.matches({cn: 'bar'}); => false f.matches({cn: 'bar'}); => false
```
Equality matching uses "strict" type JavaScript comparison, and by default Equality matching uses "strict" type JavaScript comparison, and by default
everything in ldapjs (and LDAP) is a UTF-8 string. If you want comparison everything in ldapjs (and LDAP) is a UTF-8 string. If you want comparison
@ -83,12 +89,14 @@ The string syntax for a presence filter is `(attr=*)`.
The `matches()` method will return true IFF the passed in object has a The `matches()` method will return true IFF the passed in object has a
key matching `attribute`. key matching `attribute`.
var f = new PresenceFilter({ ```js
const f = new PresenceFilter({
attribute: 'cn' attribute: 'cn'
}); });
f.matches({cn: 'foo'}); => true f.matches({cn: 'foo'}); => true
f.matches({sn: 'foo'}); => false f.matches({sn: 'foo'}); => false
```
# SubstringFilter # SubstringFilter
@ -102,24 +110,28 @@ optional. The `name` property will be `substring`.
The string syntax for a presence filter is `(attr=foo*bar*cat*dog)`, which would The string syntax for a presence filter is `(attr=foo*bar*cat*dog)`, which would
map to: map to:
{ ```js
{
initial: 'foo', initial: 'foo',
any: ['bar', 'cat'], any: ['bar', 'cat'],
final: 'dog' final: 'dog'
} }
```
The `matches()` method will return true IFF the passed in object has a The `matches()` method will return true IFF the passed in object has a
key matching `attribute` and the "regex" matches the value key matching `attribute` and the "regex" matches the value
var f = new SubstringFilter({ ```js
const f = new SubstringFilter({
attribute: 'cn', attribute: 'cn',
initial: 'foo', initial: 'foo',
any: ['bar'], any: ['bar'],
final: 'baz' final: 'baz'
}); });
f.matches({cn: 'foobigbardogbaz'}); => true f.matches({cn: 'foobigbardogbaz'}); => true
f.matches({sn: 'fobigbardogbaz'}); => false f.matches({sn: 'fobigbardogbaz'}); => false
```
# GreaterThanEqualsFilter # GreaterThanEqualsFilter
@ -135,18 +147,22 @@ property and the `name` property will be `ge`.
The string syntax for a ge filter is: The string syntax for a ge filter is:
(cn>=foo) ```
(cn>=foo)
```
The `matches()` method will return true IFF the passed in object has a The `matches()` method will return true IFF the passed in object has a
key matching `attribute` and the value is `>=` this filter's `value`. key matching `attribute` and the value is `>=` this filter's `value`.
var f = new GreaterThanEqualsFilter({ ```js
const f = new GreaterThanEqualsFilter({
attribute: 'cn', attribute: 'cn',
value: 'foo', value: 'foo',
}); });
f.matches({cn: 'foobar'}); => true f.matches({cn: 'foobar'}); => true
f.matches({cn: 'abc'}); => false f.matches({cn: 'abc'}); => false
```
# LessThanEqualsFilter # LessThanEqualsFilter
@ -159,7 +175,9 @@ Note that the ldapjs schema middleware will do this.
The string syntax for a le filter is: The string syntax for a le filter is:
(cn<=foo) ```
(cn<=foo)
```
The LessThanEqualsFilter will have an `attribute` property, a `value` The LessThanEqualsFilter will have an `attribute` property, a `value`
property and the `name` property will be `le`. property and the `name` property will be `le`.
@ -167,13 +185,15 @@ property and the `name` property will be `le`.
The `matches()` method will return true IFF the passed in object has a The `matches()` method will return true IFF the passed in object has a
key matching `attribute` and the value is `<=` this filter's `value`. key matching `attribute` and the value is `<=` this filter's `value`.
var f = new LessThanEqualsFilter({ ```js
const f = new LessThanEqualsFilter({
attribute: 'cn', attribute: 'cn',
value: 'foo', value: 'foo',
}); });
f.matches({cn: 'abc'}); => true f.matches({cn: 'abc'}); => true
f.matches({cn: 'foobar'}); => false f.matches({cn: 'foobar'}); => false
```
# AndFilter # AndFilter
@ -184,12 +204,15 @@ object will have a `filters` property which is an array of `Filter` objects. The
The string syntax for an and filter is (assuming below we're and'ing two The string syntax for an and filter is (assuming below we're and'ing two
equality filters): equality filters):
(&(cn=foo)(sn=bar)) ```
(&(cn=foo)(sn=bar))
```
The `matches()` method will return true IFF the passed in object matches all The `matches()` method will return true IFF the passed in object matches all
the filters in the `filters` array. the filters in the `filters` array.
var f = new AndFilter({ ```js
const f = new AndFilter({
filters: [ filters: [
new EqualityFilter({ new EqualityFilter({
attribute: 'cn', attribute: 'cn',
@ -200,10 +223,11 @@ the filters in the `filters` array.
value: 'bar' value: 'bar'
}) })
] ]
}); });
f.matches({cn: 'foo', sn: 'bar'}); => true f.matches({cn: 'foo', sn: 'bar'}); => true
f.matches({cn: 'foo', sn: 'baz'}); => false f.matches({cn: 'foo', sn: 'baz'}); => false
```
# OrFilter # OrFilter
@ -214,12 +238,15 @@ object will have a `filters` property which is an array of `Filter` objects. The
The string syntax for an or filter is (assuming below we're or'ing two The string syntax for an or filter is (assuming below we're or'ing two
equality filters): equality filters):
(|(cn=foo)(sn=bar)) ```
(|(cn=foo)(sn=bar))
```
The `matches()` method will return true IFF the passed in object matches *any* The `matches()` method will return true IFF the passed in object matches *any*
of the filters in the `filters` array. of the filters in the `filters` array.
var f = new OrFilter({ ```js
const f = new OrFilter({
filters: [ filters: [
new EqualityFilter({ new EqualityFilter({
attribute: 'cn', attribute: 'cn',
@ -230,10 +257,11 @@ of the filters in the `filters` array.
value: 'bar' value: 'bar'
}) })
] ]
}); });
f.matches({cn: 'foo', sn: 'baz'}); => true f.matches({cn: 'foo', sn: 'baz'}); => true
f.matches({cn: 'bar', sn: 'baz'}); => false f.matches({cn: 'bar', sn: 'baz'}); => false
```
# NotFilter # NotFilter
@ -244,20 +272,24 @@ The `name` property will be `not`.
The string syntax for a not filter is (assuming below we're not'ing an The string syntax for a not filter is (assuming below we're not'ing an
equality filter): equality filter):
(!(cn=foo)) ```
(!(cn=foo))
```
The `matches()` method will return true IFF the passed in object does not match The `matches()` method will return true IFF the passed in object does not match
the filter in the `filter` property. the filter in the `filter` property.
var f = new NotFilter({ ```js
const f = new NotFilter({
filter: new EqualityFilter({ filter: new EqualityFilter({
attribute: 'cn', attribute: 'cn',
value: 'foo' value: 'foo'
}) })
}); });
f.matches({cn: 'bar'}); => true f.matches({cn: 'bar'}); => true
f.matches({cn: 'foo'}); => false f.matches({cn: 'foo'}); => false
```
# ApproximateFilter # ApproximateFilter
@ -274,10 +306,12 @@ The string syntax for an equality filter is `(attr~=value)`.
The `matches()` method will return true IFF the passed in object has a The `matches()` method will return true IFF the passed in object has a
key matching `attribute` and a value exactly matching `value`. key matching `attribute` and a value exactly matching `value`.
var f = new ApproximateFilter({ ```js
const f = new ApproximateFilter({
attribute: 'cn', attribute: 'cn',
value: 'foo' value: 'foo'
}); });
f.matches({cn: 'foo'}); => true f.matches({cn: 'foo'}); => true
f.matches({cn: 'bar'}); => false f.matches({cn: 'bar'}); => false
```

View File

@ -31,23 +31,26 @@ Access Protocol". A directory service basically breaks down as follows:
It might be helpful to visualize: It might be helpful to visualize:
```
o=example o=example
/ \ / \
ou=users ou=groups ou=users ou=groups
/ | | \ / | | \
cn=john cn=jane cn=dudes cn=dudettes cn=john cn=jane cn=dudes cn=dudettes
/ /
keyid=foo keyid=foo
```
Let's say we wanted to look at the record cn=john: Let's say we wanted to look at the record cn=john:
dn: cn=john, ou=users, o=example ```sh
cn: john dn: cn=john, ou=users, o=example
sn: smith cn: john
email: john@example.com sn: smith
email: john.smith@example.com email: john@example.com
objectClass: person email: john.smith@example.com
objectClass: person
```
A few things to note: A few things to note:
@ -111,7 +114,9 @@ If you don't already have node.js and npm, clearly you need those, so follow
the steps at [nodejs.org](http://nodejs.org) and [npmjs.org](http://npmjs.org), the steps at [nodejs.org](http://nodejs.org) and [npmjs.org](http://npmjs.org),
respectively. After that, run: respectively. After that, run:
$ npm install ldapjs ```sh
$ npm install ldapjs
```
Rather than overload you with client-side programming for now, we'll use Rather than overload you with client-side programming for now, we'll use
the OpenLDAP CLI to interact with our server. It's almost certainly already the OpenLDAP CLI to interact with our server. It's almost certainly already
@ -121,18 +126,22 @@ package manager here.
To get started, open some file, and let's get the library loaded and a server To get started, open some file, and let's get the library loaded and a server
created: created:
var ldap = require('ldapjs'); ```js
const ldap = require('ldapjs');
var server = ldap.createServer(); const server = ldap.createServer();
server.listen(1389, function() { server.listen(1389, () => {
console.log('/etc/passwd LDAP server up at: %s', server.url); console.log('/etc/passwd LDAP server up at: %s', server.url);
}); });
```
And run that. Doing anything will give you errors (LDAP "No Such Object") And run that. Doing anything will give you errors (LDAP "No Such Object")
since we haven't added any support in yet, but go ahead and try it anyway: since we haven't added any support in yet, but go ahead and try it anyway:
$ ldapsearch -H ldap://localhost:1389 -x -b "o=myhost" objectclass=* ```sh
$ ldapsearch -H ldap://localhost:1389 -x -b "o=myhost" objectclass=*
```
Before we go any further, note that the complete code for the server we are Before we go any further, note that the complete code for the server we are
about to build up is on the [examples](examples.html) page. about to build up is on the [examples](examples.html) page.
@ -153,13 +162,15 @@ has no correspondence to our Unix root user, it's just something we're making up
and going to use for allowing an (LDAP) admin to do anything. To do so, add and going to use for allowing an (LDAP) admin to do anything. To do so, add
this code into your file: this code into your file:
server.bind('cn=root', function(req, res, next) { ```js
server.bind('cn=root', (req, res, next) => {
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret') if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
return next(new ldap.InvalidCredentialsError()); return next(new ldap.InvalidCredentialsError());
res.end(); res.end();
return next(); return next();
}); });
```
Not very secure, but this is a demo. What we did there was "mount" a tree in Not very secure, but this is a demo. What we did there was "mount" a tree in
the ldapjs server, and add a handler for the _bind_ method. If you've ever used the ldapjs server, and add a handler for the _bind_ method. If you've ever used
@ -168,7 +179,9 @@ handlers in, as we'll see later.
On to the meat of the method. What's up with this? On to the meat of the method. What's up with this?
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret') ```js
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
```
The first part `req.dn.toString() !== 'cn=root'`: you're probably thinking The first part `req.dn.toString() !== 'cn=root'`: you're probably thinking
"WTF?!? Does ldapjs allow something other than cn=root into this handler?" Sort "WTF?!? Does ldapjs allow something other than cn=root into this handler?" Sort
@ -192,18 +205,22 @@ add another handler in later you won't get bit by it not being invoked.
Blah blah, let's try running the ldap client again, first with a bad password: Blah blah, let's try running the ldap client again, first with a bad password:
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w foo -b "o=myhost" objectclass=* ```sh
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w foo -b "o=myhost" objectclass=*
ldap_bind: Invalid credentials (49) ldap_bind: Invalid credentials (49)
matched DN: cn=root matched DN: cn=root
additional info: Invalid Credentials additional info: Invalid Credentials
```
And again with the correct one: And again with the correct one:
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" objectclass=* ```sh
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" objectclass=*
No such object (32) No such object (32)
Additional information: No tree found for: o=myhost Additional information: No tree found for: o=myhost
```
Don't worry about all the flags we're passing into OpenLDAP, that's just to make Don't worry about all the flags we're passing into OpenLDAP, that's just to make
their CLI less annonyingly noisy. This time, we got another `No such object` their CLI less annonyingly noisy. This time, we got another `No such object`
@ -217,12 +234,14 @@ what if the remote end doesn't authenticate at all? Right, nothing says they
*have to* bind, that's just what the common clients do. Let's add a quick *have to* bind, that's just what the common clients do. Let's add a quick
authorization handler that we'll use in all our subsequent routes: authorization handler that we'll use in all our subsequent routes:
function authorize(req, res, next) { ```js
function authorize(req, res, next) {
if (!req.connection.ldap.bindDN.equals('cn=root')) if (!req.connection.ldap.bindDN.equals('cn=root'))
return next(new ldap.InsufficientAccessRightsError()); return next(new ldap.InsufficientAccessRightsError());
return next(); return next();
} }
```
Should be pretty self-explanatory, but as a reminder, LDAP is connection Should be pretty self-explanatory, but as a reminder, LDAP is connection
oriented, so we check that the connection remote user was indeed our `cn=root` oriented, so we check that the connection remote user was indeed our `cn=root`
@ -233,7 +252,9 @@ oriented, so we check that the connection remote user was indeed our `cn=root`
We said we wanted to allow LDAP operations over /etc/passwd, so let's detour We said we wanted to allow LDAP operations over /etc/passwd, so let's detour
for a moment to explain an /etc/passwd record. for a moment to explain an /etc/passwd record.
jsmith:x:1001:1000:Joe Smith,Room 1007,(234)555-8910,(234)555-0044,email:/home/jsmith:/bin/sh ```sh
jsmith:x:1001:1000:Joe Smith,Room 1007,(234)555-8910,(234)555-0044,email:/home/jsmith:/bin/sh
```
The sample record above maps to: The sample record above maps to:
@ -248,24 +269,25 @@ The sample record above maps to:
|/bin/sh |Shell | |/bin/sh |Shell |
Let's write some handlers to parse that and transform it into an LDAP search Let's write some handlers to parse that and transform it into an LDAP search
record (note, you'll need to add `var fs = require('fs');` at the top of the record (note, you'll need to add `const fs = require('fs');` at the top of the
source file). source file).
First, make a handler that just loads the "user database" in a "pre" handler: First, make a handler that just loads the "user database" in a "pre" handler:
function loadPasswdFile(req, res, next) { ```js
fs.readFile('/etc/passwd', 'utf8', function(err, data) { function loadPasswdFile(req, res, next) {
fs.readFile('/etc/passwd', 'utf8', (err, data) => {
if (err) if (err)
return next(new ldap.OperationsError(err.message)); return next(new ldap.OperationsError(err.message));
req.users = {}; req.users = {};
var lines = data.split('\n'); const lines = data.split('\n');
for (var i = 0; i < lines.length; i++) { for (const line of lines) {
if (!lines[i] || /^#/.test(lines[i])) if (!line || /^#/.test(line))
continue; continue;
var record = lines[i].split(':'); const record = line.split(':');
if (!record || !record.length) if (!record || !record.length)
continue; continue;
@ -285,40 +307,48 @@ First, make a handler that just loads the "user database" in a "pre" handler:
return next(); return next();
}); });
} }
```
Ok, all that did is tack the /etc/passwd records onto req.users so that any Ok, all that did is tack the /etc/passwd records onto req.users so that any
subsequent handler doesn't have to reload the file. Next, let's write a search subsequent handler doesn't have to reload the file. Next, let's write a search
handler to process that: handler to process that:
var pre = [authorize, loadPasswdFile]; ```js
const pre = [authorize, loadPasswdFile];
server.search('o=myhost', pre, function(req, res, next) { server.search('o=myhost', pre, (req, res, next) => {
Object.keys(req.users).forEach(function(k) { const keys = Object.keys(req.users);
for (const k of keys) {
if (req.filter.matches(req.users[k].attributes)) if (req.filter.matches(req.users[k].attributes))
res.send(req.users[k]); res.send(req.users[k]);
}); }
res.end(); res.end();
return next(); return next();
}); });
```
And try running: And try running:
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" cn=root ```sh
dn: cn=root, ou=users, o=myhost $ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" cn=root
cn: root dn: cn=root, ou=users, o=myhost
uid: 0 cn: root
gid: 0 uid: 0
description: System Administrator gid: 0
homedirectory: /var/root description: System Administrator
shell: /bin/sh homedirectory: /var/root
objectclass: unixUser shell: /bin/sh
objectclass: unixUser
```
Sweet! Try this out too: Sweet! Try this out too:
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" objectclass=* ```sh
... $ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" objectclass=*
...
```
You should have seen an entry for every record in /etc/passwd with the second. You should have seen an entry for every record in /etc/passwd with the second.
What all did we do here? A lot. Let's break this down... What all did we do here? A lot. Let's break this down...
@ -327,7 +357,9 @@ What all did we do here? A lot. Let's break this down...
Let's start with looking at what you even asked for: Let's start with looking at what you even asked for:
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" cn=root ```sh
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" cn=root
```
We can throw away `ldapsearch -H -x -D -w -LLL`, as those just specify the URL We can throw away `ldapsearch -H -x -D -w -LLL`, as those just specify the URL
to connect to, the bind credentials and the `-LLL` just quiets down OpenLDAP. to connect to, the bind credentials and the `-LLL` just quiets down OpenLDAP.
@ -360,7 +392,8 @@ and made the cheesiest transform ever, which is making up a "search entry." A
search entry _must_ have a DN so the client knows what record it is, and a set search entry _must_ have a DN so the client knows what record it is, and a set
of attributes. So that's why we did this: of attributes. So that's why we did this:
var entry = { ```js
const entry = {
dn: 'cn=' + record[0] + ', ou=users, o=myhost', dn: 'cn=' + record[0] + ', ou=users, o=myhost',
attributes: { attributes: {
cn: record[0], cn: record[0],
@ -371,7 +404,8 @@ of attributes. So that's why we did this:
shell: record[6] || '', shell: record[6] || '',
objectclass: 'unixUser' objectclass: 'unixUser'
} }
}; };
```
Next, we let ldapjs do all the hard work of figuring out LDAP search filters Next, we let ldapjs do all the hard work of figuring out LDAP search filters
for us by calling `req.filter.matches`. If it matched, we return the whole for us by calling `req.filter.matches`. If it matched, we return the whole
@ -386,35 +420,38 @@ shell set to `/bin/false` and whose name starts with `p` (I'm doing this
on Ubuntu). Then, let's say we only care about their login name and primary on Ubuntu). Then, let's say we only care about their login name and primary
group id. We'd do this: group id. We'd do this:
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" "(&(shell=/bin/false)(cn=p*))" cn gid ```sh
dn: cn=proxy, ou=users, o=myhost $ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" "(&(shell=/bin/false)(cn=p*))" cn gid
cn: proxy dn: cn=proxy, ou=users, o=myhost
gid: 13 cn: proxy
gid: 13
dn: cn=pulse, ou=users, o=myhost dn: cn=pulse, ou=users, o=myhost
cn: pulse cn: pulse
gid: 114 gid: 114
```
## Add ## Add
This is going to be a little bit ghetto, since what we're going to do is just This is going to be a little bit ghetto, since what we're going to do is just
use node's child process module to spawn calls to `adduser`. Go ahead and add use node's child process module to spawn calls to `adduser`. Go ahead and add
the following code in as another handler (you'll need a the following code in as another handler (you'll need a
`var spawn = require('child_process').spawn;` at the top of your file): `const { spawn } = require('child_process');` at the top of your file):
server.add('ou=users, o=myhost', pre, function(req, res, next) { ```js
server.add('ou=users, o=myhost', pre, (req, res, next) => {
if (!req.dn.rdns[0].attrs.cn) if (!req.dn.rdns[0].attrs.cn)
return next(new ldap.ConstraintViolationError('cn required')); return next(new ldap.ConstraintViolationError('cn required'));
if (req.users[req.dn.rdns[0].attrs.cn.value]) if (req.users[req.dn.rdns[0].attrs.cn.value])
return next(new ldap.EntryAlreadyExistsError(req.dn.toString())); return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
var entry = req.toObject().attributes; const entry = req.toObject().attributes;
if (entry.objectclass.indexOf('unixUser') === -1) if (entry.objectclass.indexOf('unixUser') === -1)
return next(new ldap.ConstraintViolationError('entry must be a unixUser')); return next(new ldap.ConstraintViolationError('entry must be a unixUser'));
var opts = ['-m']; const opts = ['-m'];
if (entry.description) { if (entry.description) {
opts.push('-c'); opts.push('-c');
opts.push(entry.description[0]); opts.push(entry.description[0]);
@ -436,20 +473,20 @@ the following code in as another handler (you'll need a
opts.push(entry.uid[0]); opts.push(entry.uid[0]);
} }
opts.push(entry.cn[0]); opts.push(entry.cn[0]);
var useradd = spawn('useradd', opts); const useradd = spawn('useradd', opts);
var messages = []; const messages = [];
useradd.stdout.on('data', function(data) { useradd.stdout.on('data', (data) => {
messages.push(data.toString()); messages.push(data.toString());
}); });
useradd.stderr.on('data', function(data) { useradd.stderr.on('data', (data) => {
messages.push(data.toString()); messages.push(data.toString());
}); });
useradd.on('exit', function(code) { useradd.on('exit', (code) => {
if (code !== 0) { if (code !== 0) {
var msg = '' + code; let msg = '' + code;
if (messages.length) if (messages.length)
msg += ': ' + messages.join(); msg += ': ' + messages.join();
return next(new ldap.OperationsError(msg)); return next(new ldap.OperationsError(msg));
@ -458,48 +495,58 @@ the following code in as another handler (you'll need a
res.end(); res.end();
return next(); return next();
}); });
}); });
```
Then, you'll need to be root to have this running, so start your server with Then, you'll need to be root to have this running, so start your server with
`sudo` (or be root, whatever). Now, go ahead and create a file called `sudo` (or be root, whatever). Now, go ahead and create a file called
`user.ldif` with the following contents: `user.ldif` with the following contents:
dn: cn=ldapjs, ou=users, o=myhost ```sh
objectClass: unixUser dn: cn=ldapjs, ou=users, o=myhost
cn: ldapjs objectClass: unixUser
shell: /bin/bash cn: ldapjs
description: Created via ldapadd shell: /bin/bash
description: Created via ldapadd
```
Now go ahead and invoke with: Now go ahead and invoke with:
$ ldapadd -H ldap://localhost:1389 -x -D cn=root -w secret -f ./user.ldif ```sh
adding new entry "cn=ldapjs, ou=users, o=myhost" $ ldapadd -H ldap://localhost:1389 -x -D cn=root -w secret -f ./user.ldif
adding new entry "cn=ldapjs, ou=users, o=myhost"
```
Let's confirm he got added with an ldapsearch: Let's confirm he got added with an ldapsearch:
$ ldapsearch -H ldap://localhost:1389 -LLL -x -D cn=root -w secret -b "ou=users, o=myhost" cn=ldapjs ```sh
dn: cn=ldapjs, ou=users, o=myhost $ ldapsearch -H ldap://localhost:1389 -LLL -x -D cn=root -w secret -b "ou=users, o=myhost" cn=ldapjs
cn: ldapjs dn: cn=ldapjs, ou=users, o=myhost
uid: 1001 cn: ldapjs
gid: 1001 uid: 1001
description: Created via ldapadd gid: 1001
homedirectory: /home/ldapjs description: Created via ldapadd
shell: /bin/bash homedirectory: /home/ldapjs
objectclass: unixUser shell: /bin/bash
objectclass: unixUser
```
As before, here's a breakdown of the code: As before, here's a breakdown of the code:
server.add('ou=users, o=myhost', pre, function(req, res, next) { ```js
server.add('ou=users, o=myhost', pre, (req, res, next) => {
if (!req.dn.rdns[0].attrs.cn) if (!req.dn.rdns[0].attrs.cn)
return next(new ldap.ConstraintViolationError('cn required')); return next(new ldap.ConstraintViolationError('cn required'));
if (req.users[req.dn.rdns[0].attrs.cn.value]) if (req.users[req.dn.rdns[0].attrs.cn.value])
return next(new ldap.EntryAlreadyExistsError(req.dn.toString())); return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
var entry = req.toObject().attributes; const entry = req.toObject().attributes;
if (entry.objectclass.indexOf('unixUser') === -1) if (entry.objectclass.indexOf('unixUser') === -1)
return next(new ldap.ConstraintViolationError('entry must be a unixUser')); return next(new ldap.ConstraintViolationError('entry must be a unixUser'));
});
```
A few new things: A few new things:
@ -534,17 +581,18 @@ Unlike HTTP, "partial" document updates are fully specified as part of the
RFC, so appending, removing, or replacing a single attribute is pretty natural. RFC, so appending, removing, or replacing a single attribute is pretty natural.
Go ahead and add the following code into your source file: Go ahead and add the following code into your source file:
server.modify('ou=users, o=myhost', pre, function(req, res, next) { ```js
server.modify('ou=users, o=myhost', pre, (req, res, next) => {
if (!req.dn.rdns[0].attrs.cn || !req.users[req.dn.rdns[0].attrs.cn.value]) if (!req.dn.rdns[0].attrs.cn || !req.users[req.dn.rdns[0].attrs.cn.value])
return next(new ldap.NoSuchObjectError(req.dn.toString())); return next(new ldap.NoSuchObjectError(req.dn.toString()));
if (!req.changes.length) if (!req.changes.length)
return next(new ldap.ProtocolError('changes required')); return next(new ldap.ProtocolError('changes required'));
var user = req.users[req.dn.rdns[0].attrs.cn.value].attributes; const user = req.users[req.dn.rdns[0].attrs.cn.value].attributes;
var mod; let mod;
for (var i = 0; i < req.changes.length; i++) { for (const i = 0; i < req.changes.length; i++) {
mod = req.changes[i].modification; mod = req.changes[i].modification;
switch (req.changes[i].operation) { switch (req.changes[i].operation) {
case 'replace': case 'replace':
@ -558,18 +606,18 @@ Go ahead and add the following code into your source file:
} }
} }
var passwd = spawn('chpasswd', ['-c', 'MD5']); const passwd = spawn('chpasswd', ['-c', 'MD5']);
passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8'); passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8');
passwd.on('exit', function(code) { passwd.on('exit', (code) => {
if (code !== 0) if (code !== 0)
return next(new ldap.OperationsError(code)); return next(new ldap.OperationsError(code));
res.end(); res.end();
return next(); return next();
}); });
}); });
```
Basically, we made sure the remote client was targeting an entry that exists, Basically, we made sure the remote client was targeting an entry that exists,
ensuring that they were asking to "replace" the `userPassword` attribute (which ensuring that they were asking to "replace" the `userPassword` attribute (which
@ -578,15 +626,19 @@ is the 'standard' LDAP attribute for passwords; if you think it's easier to use
command (which lets you change a user's password over stdin). Next, go ahead command (which lets you change a user's password over stdin). Next, go ahead
and create a `passwd.ldif` file: and create a `passwd.ldif` file:
dn: cn=ldapjs, ou=users, o=myhost ```sh
changetype: modify dn: cn=ldapjs, ou=users, o=myhost
replace: userPassword changetype: modify
userPassword: secret replace: userPassword
- userPassword: secret
-
```
And then run the OpenLDAP CLI: And then run the OpenLDAP CLI:
$ ldapmodify -H ldap://localhost:1389 -x -D cn=root -w secret -f ./passwd.ldif ```sh
$ ldapmodify -H ldap://localhost:1389 -x -D cn=root -w secret -f ./passwd.ldif
```
You should now be able to login to your box as the ldapjs user. Let's get You should now be able to login to your box as the ldapjs user. Let's get
the last "mainline" piece of work out of the way, and delete the user. the last "mainline" piece of work out of the way, and delete the user.
@ -596,23 +648,24 @@ the last "mainline" piece of work out of the way, and delete the user.
Delete is pretty straightforward. The client gives you a dn to delete, and you Delete is pretty straightforward. The client gives you a dn to delete, and you
delete it :). Add the following code into your server: delete it :). Add the following code into your server:
server.del('ou=users, o=myhost', pre, function(req, res, next) { ```js
server.del('ou=users, o=myhost', pre, (req, res, next) => {
if (!req.dn.rdns[0].attrs.cn || !req.users[req.dn.rdns[0].attrs.cn.value]) if (!req.dn.rdns[0].attrs.cn || !req.users[req.dn.rdns[0].attrs.cn.value])
return next(new ldap.NoSuchObjectError(req.dn.toString())); return next(new ldap.NoSuchObjectError(req.dn.toString()));
var userdel = spawn('userdel', ['-f', req.dn.rdns[0].attrs.cn.value]); const userdel = spawn('userdel', ['-f', req.dn.rdns[0].attrs.cn.value]);
var messages = []; const messages = [];
userdel.stdout.on('data', function(data) { userdel.stdout.on('data', (data) => {
messages.push(data.toString()); messages.push(data.toString());
}); });
userdel.stderr.on('data', function(data) { userdel.stderr.on('data', (data) => {
messages.push(data.toString()); messages.push(data.toString());
}); });
userdel.on('exit', function(code) { userdel.on('exit', (code) => {
if (code !== 0) { if (code !== 0) {
var msg = '' + code; let msg = '' + code;
if (messages.length) if (messages.length)
msg += ': ' + messages.join(); msg += ': ' + messages.join();
return next(new ldap.OperationsError(msg)); return next(new ldap.OperationsError(msg));
@ -621,12 +674,14 @@ delete it :). Add the following code into your server:
res.end(); res.end();
return next(); return next();
}); });
}); });
```
And then run the following command: And then run the following command:
$ ldapdelete -H ldap://localhost:1389 -x -D cn=root -w secret "cn=ldapjs, ou=users, o=myhost" ```sh
$ ldapdelete -H ldap://localhost:1389 -x -D cn=root -w secret "cn=ldapjs, ou=users, o=myhost"
```
# Where to go from here # Where to go from here

View File

@ -17,12 +17,13 @@ with HTTP services in node and [restify](http://restify.com).
</div> </div>
var ldap = require('ldapjs'); ```js
const ldap = require('ldapjs');
var server = ldap.createServer(); const server = ldap.createServer();
server.search('o=example', function(req, res, next) { server.search('o=example', (req, res, next) => {
var obj = { const obj = {
dn: req.dn.toString(), dn: req.dn.toString(),
attributes: { attributes: {
objectclass: ['organization', 'top'], objectclass: ['organization', 'top'],
@ -34,15 +35,18 @@ with HTTP services in node and [restify](http://restify.com).
res.send(obj); res.send(obj);
res.end(); res.end();
}); });
server.listen(1389, function() { server.listen(1389, () => {
console.log('LDAP server listening at %s', server.url); console.log('LDAP server listening at %s', server.url);
}); });
```
Try hitting that with: Try hitting that with:
$ ldapsearch -H ldap://localhost:1389 -x -b o=example objectclass=* ```sh
$ ldapsearch -H ldap://localhost:1389 -x -b o=example objectclass=*
```
# Features # Features
@ -55,7 +59,9 @@ that you can build LDAP over anything you want, not just traditional databases.
# Getting started # Getting started
$ npm install ldapjs ```sh
$ npm install ldapjs
```
If you're new to LDAP, check out the [guide](guide.html). Otherwise, the If you're new to LDAP, check out the [guide](guide.html). Otherwise, the
API documentation is: API documentation is:

View File

@ -15,7 +15,9 @@ with LDAP. If you're not, read the [guide](guide.html) first.
The code to create a new server looks like: The code to create a new server looks like:
var server = ldap.createServer(); ```js
const server = ldap.createServer();
```
The full list of options is: The full list of options is:
@ -68,10 +70,12 @@ available.
Example: Example:
```js
server.listen(389, '127.0.0.1', function() { server.listen(389, '127.0.0.1', function() {
console.log('LDAP server listening at: ' + server.url); console.log('LDAP server listening at: ' + server.url);
}); });
```
### Port and Host ### Port and Host
`listen(port, [host], [callback])` `listen(port, [host], [callback])`
@ -115,14 +119,16 @@ paradigm of programming. Essentially every method is of the form
handlers together by calling `next()` and ordering your functions in the handlers together by calling `next()` and ordering your functions in the
definition of the route. For example: definition of the route. For example:
function authorize(req, res, next) { ```js
function authorize(req, res, next) {
if (!req.connection.ldap.bindDN.equals('cn=root')) if (!req.connection.ldap.bindDN.equals('cn=root'))
return next(new ldap.InsufficientAccessRightsError()); return next(new ldap.InsufficientAccessRightsError());
return next(); return next();
} }
server.search('o=example', authorize, function(req, res, next) { ... }); server.search('o=example', authorize, function(req, res, next) { ... });
```
Note that ldapjs is also slightly different, since it's often going to be backed Note that ldapjs is also slightly different, since it's often going to be backed
to a DB-like entity, in that it also has an API where you can pass in a to a DB-like entity, in that it also has an API where you can pass in a
@ -134,23 +140,25 @@ complete implementation of the LDAP protocol over
[Riak](https://github.com/basho/riak). Getting an LDAP server up with riak [Riak](https://github.com/basho/riak). Getting an LDAP server up with riak
looks like: looks like:
var ldap = require('ldapjs'); ```js
var ldapRiak = require('ldapjs-riak'); const ldap = require('ldapjs');
const ldapRiak = require('ldapjs-riak');
var server = ldap.createServer(); const server = ldap.createServer();
var backend = ldapRiak.createBackend({ const backend = ldapRiak.createBackend({
"host": "localhost", "host": "localhost",
"port": 8098, "port": 8098,
"bucket": "example", "bucket": "example",
"indexes": ["l", "cn"], "indexes": ["l", "cn"],
"uniqueIndexes": ["uid"], "uniqueIndexes": ["uid"],
"numConnections": 5 "numConnections": 5
}); });
server.add("o=example", server.add("o=example",
backend, backend,
backend.add()); backend.add());
... ...
```
The first parameter to an ldapjs route is always the point in the The first parameter to an ldapjs route is always the point in the
tree to mount the handler chain at. The second argument is _optionally_ a tree to mount the handler chain at. The second argument is _optionally_ a
@ -163,10 +171,12 @@ operation requires specific methods/fields on the request/response
objects. However, there is a `.use()` method availabe, similar to objects. However, there is a `.use()` method availabe, similar to
that on express/connect, allowing you to chain up "middleware": that on express/connect, allowing you to chain up "middleware":
server.use(function(req, res, next) { ```js
server.use(function(req, res, next) {
console.log('hello world'); console.log('hello world');
return next(); return next();
}); });
```
## Common Request Elements ## Common Request Elements
@ -210,11 +220,13 @@ the paradigm is something defined like CONSTRAINT\_VIOLATION in the RFC would be
`ConstraintViolationError` in ldapjs. Upon calling `next(new LDAPError())`, `ConstraintViolationError` in ldapjs. Upon calling `next(new LDAPError())`,
ldapjs will _stop_ calling your handler chain. For example: ldapjs will _stop_ calling your handler chain. For example:
server.search('o=example', ```js
function(req, res, next) { return next(); }, server.search('o=example',
function(req, res, next) { return next(new ldap.OperationsError()); }, (req, res, next) => { return next(); },
function(req, res, next) { res.end(); } (req, res, next) => { return next(new ldap.OperationsError()); },
); (req, res, next) => { res.end(); }
);
```
In the code snipped above, the third handler would never get invoked. In the code snipped above, the third handler would never get invoked.
@ -222,11 +234,13 @@ In the code snipped above, the third handler would never get invoked.
Adds a mount in the tree to perform LDAP binds with. Example: Adds a mount in the tree to perform LDAP binds with. Example:
server.bind('ou=people, o=example', function(req, res, next) { ```js
server.bind('ou=people, o=example', (req, res, next) => {
console.log('bind DN: ' + req.dn.toString()); console.log('bind DN: ' + req.dn.toString());
console.log('bind PW: ' + req.credentials); console.log('bind PW: ' + req.credentials);
res.end(); res.end();
}); });
```
## BindRequest ## BindRequest
@ -259,11 +273,13 @@ No extra methods above an `LDAPResult` API call.
Adds a mount in the tree to perform LDAP adds with. Adds a mount in the tree to perform LDAP adds with.
server.add('ou=people, o=example', function(req, res, next) { ```js
server.add('ou=people, o=example', (req, res, next) => {
console.log('DN: ' + req.dn.toString()); console.log('DN: ' + req.dn.toString());
console.log('Entry attributes: ' + req.toObject().attributes); console.log('Entry attributes: ' + req.toObject().attributes);
res.end(); res.end();
}); });
```
## AddRequest ## AddRequest
@ -287,14 +303,16 @@ a standard JavaScript object.
This operation will return a plain JavaScript object from the request that looks This operation will return a plain JavaScript object from the request that looks
like: like:
{ ```js
{
dn: 'cn=foo, o=example', // string, not DN object dn: 'cn=foo, o=example', // string, not DN object
attributes: { attributes: {
cn: ['foo'], cn: ['foo'],
sn: ['bar'], sn: ['bar'],
objectclass: ['person', 'top'] objectclass: ['person', 'top']
} }
} }
```
## AddResponse ## AddResponse
@ -304,12 +322,14 @@ No extra methods above an `LDAPResult` API call.
Adds a handler for the LDAP search operation. Adds a handler for the LDAP search operation.
server.search('o=example', function(req, res, next) { ```js
server.search('o=example', (req, res, next) => {
console.log('base object: ' + req.dn.toString()); console.log('base object: ' + req.dn.toString());
console.log('scope: ' + req.scope); console.log('scope: ' + req.scope);
console.log('filter: ' + req.filter.toString()); console.log('filter: ' + req.filter.toString());
res.end(); res.end();
}); });
```
## SearchRequest ## SearchRequest
@ -367,8 +387,9 @@ explicitly pass in a `SearchEntry` object, and can instead just send a plain
JavaScript object that matches the format used from `AddRequest.toObject()`. JavaScript object that matches the format used from `AddRequest.toObject()`.
server.search('o=example', function(req, res, next) { ```js
var obj = { server.search('o=example', (req, res, next) => {
const obj = {
dn: 'o=example', dn: 'o=example',
attributes: { attributes: {
objectclass: ['top', 'organization'], objectclass: ['top', 'organization'],
@ -380,21 +401,24 @@ JavaScript object that matches the format used from `AddRequest.toObject()`.
res.send(obj) res.send(obj)
res.end(); res.end();
}); });
```
# modify # modify
Allows you to handle an LDAP modify operation. Allows you to handle an LDAP modify operation.
server.modify('o=example', function(req, res, next) { ```js
server.modify('o=example', (req, res, next) => {
console.log('DN: ' + req.dn.toString()); console.log('DN: ' + req.dn.toString());
console.log('changes:'); console.log('changes:');
req.changes.forEach(function(c) { for (const c of req.changes) {
console.log(' operation: ' + c.operation); console.log(' operation: ' + c.operation);
console.log(' modification: ' + c.modification.toString()); console.log(' modification: ' + c.modification.toString());
}); }
res.end(); res.end();
}); });
```
## ModifyRequest ## ModifyRequest
@ -431,10 +455,12 @@ No extra methods above an `LDAPResult` API call.
Allows you to handle an LDAP delete operation. Allows you to handle an LDAP delete operation.
server.del('o=example', function(req, res, next) { ```js
server.del('o=example', (req, res, next) => {
console.log('DN: ' + req.dn.toString()); console.log('DN: ' + req.dn.toString());
res.end(); res.end();
}); });
```
## DeleteRequest ## DeleteRequest
@ -451,12 +477,14 @@ No extra methods above an `LDAPResult` API call.
Allows you to handle an LDAP compare operation. Allows you to handle an LDAP compare operation.
server.compare('o=example', function(req, res, next) { ```js
server.compare('o=example', (req, res, next) => {
console.log('DN: ' + req.dn.toString()); console.log('DN: ' + req.dn.toString());
console.log('attribute name: ' + req.attribute); console.log('attribute name: ' + req.attribute);
console.log('attribute value: ' + req.value); console.log('attribute value: ' + req.value);
res.end(req.value === 'foo'); res.end(req.value === 'foo');
}); });
```
## CompareRequest ## CompareRequest
@ -483,7 +511,8 @@ that, there are no extra methods above an `LDAPResult` API call.
Allows you to handle an LDAP modifyDN operation. Allows you to handle an LDAP modifyDN operation.
server.modifyDN('o=example', function(req, res, next) { ```js
server.modifyDN('o=example', (req, res, next) => {
console.log('DN: ' + req.dn.toString()); console.log('DN: ' + req.dn.toString());
console.log('new RDN: ' + req.newRdn.toString()); console.log('new RDN: ' + req.newRdn.toString());
console.log('deleteOldRDN: ' + req.deleteOldRdn); console.log('deleteOldRDN: ' + req.deleteOldRdn);
@ -491,7 +520,8 @@ Allows you to handle an LDAP modifyDN operation.
(req.newSuperior ? req.newSuperior.toString() : '')); (req.newSuperior ? req.newSuperior.toString() : ''));
res.end(); res.end();
}); });
```
## ModifyDNRequest ## ModifyDNRequest
@ -525,14 +555,16 @@ OID, but ldapjs makes no such restrictions; it just needs to be a string.
Unlike the other operations, extended operations don't map to any location in Unlike the other operations, extended operations don't map to any location in
the tree, so routing here will be exact match, as opposed to subtree. the tree, so routing here will be exact match, as opposed to subtree.
// LDAP whoami ```js
server.exop('1.3.6.1.4.1.4203.1.11.3', function(req, res, next) { // LDAP whoami
server.exop('1.3.6.1.4.1.4203.1.11.3', (req, res, next) => {
console.log('name: ' + req.name); console.log('name: ' + req.name);
console.log('value: ' + req.value); console.log('value: ' + req.value);
res.value = 'u:xxyyz@EXAMPLE.NET'; res.value = 'u:xxyyz@EXAMPLE.NET';
res.end(); res.end();
return next(); return next();
}); });
```
## ExtendedRequest ## ExtendedRequest
@ -563,9 +595,11 @@ and cleans up any internals (in ldapjs core). You can override this handler
if you need to clean up any items in your backend, or perform any other cleanup if you need to clean up any items in your backend, or perform any other cleanup
tasks you need to. tasks you need to.
server.unbind(function(req, res, next) { ```js
server.unbind((req, res, next) => {
res.end(); res.end();
}); });
```
Note that the LDAP unbind operation actually doesn't send any response (by Note that the LDAP unbind operation actually doesn't send any response (by
definition in the RFC), so the UnbindResponse is really just a stub that definition in the RFC), so the UnbindResponse is really just a stub that