docs: update code
This commit is contained in:
parent
2ee04b14a3
commit
e4b72cde9b
152
docs/client.md
152
docs/client.md
|
@ -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);
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
34
docs/dn.md
34
docs/dn.md
|
@ -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());
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
|
@ -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.
|
||||||
|
|
333
docs/examples.md
333
docs/examples.md
|
@ -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=*
|
||||||
|
```
|
||||||
|
|
126
docs/filters.md
126
docs/filters.md
|
@ -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
|
||||||
|
```
|
||||||
|
|
279
docs/guide.md
279
docs/guide.md
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
122
docs/server.md
122
docs/server.md
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue