docs for dns, errors, filters and server, and minor fixes found along the way
This commit is contained in:
parent
7198c0a516
commit
7859f34b8a
4
Makefile
4
Makefile
|
@ -53,8 +53,12 @@ doc: dep
|
||||||
@rm -rf ${DOCPKGDIR}
|
@rm -rf ${DOCPKGDIR}
|
||||||
@mkdir -p ${DOCPKGDIR}
|
@mkdir -p ${DOCPKGDIR}
|
||||||
${RESTDOWN} -m ${DOCPKGDIR} -D mediaroot=media ./docs/client.md
|
${RESTDOWN} -m ${DOCPKGDIR} -D mediaroot=media ./docs/client.md
|
||||||
|
${RESTDOWN} -m ${DOCPKGDIR} -D mediaroot=media ./docs/dn.md
|
||||||
|
${RESTDOWN} -m ${DOCPKGDIR} -D mediaroot=media ./docs/errors.md
|
||||||
${RESTDOWN} -m ${DOCPKGDIR} -D mediaroot=media ./docs/examples.md
|
${RESTDOWN} -m ${DOCPKGDIR} -D mediaroot=media ./docs/examples.md
|
||||||
|
${RESTDOWN} -m ${DOCPKGDIR} -D mediaroot=media ./docs/filters.md
|
||||||
${RESTDOWN} -m ${DOCPKGDIR} -D mediaroot=media ./docs/guide.md
|
${RESTDOWN} -m ${DOCPKGDIR} -D mediaroot=media ./docs/guide.md
|
||||||
|
${RESTDOWN} -m ${DOCPKGDIR} -D mediaroot=media ./docs/server.md
|
||||||
rm docs/*.json
|
rm docs/*.json
|
||||||
mv docs/*.html ${DOCPKGDIR}
|
mv docs/*.html ${DOCPKGDIR}
|
||||||
(cd ${DOCPKGDIR} && $(TAR) -czf ${SRC}/${NAME}-docs-`git log -1 --pretty='format:%h'`.tar.gz *)
|
(cd ${DOCPKGDIR} && $(TAR) -czf ${SRC}/${NAME}-docs-`git log -1 --pretty='format:%h'`.tar.gz *)
|
||||||
|
|
|
@ -14,7 +14,7 @@ LDAP, so if you're not, hit the [guide](http://ldapjs.org/guide.html) first.
|
||||||
|
|
||||||
# Create a client
|
# Create a client
|
||||||
|
|
||||||
The code to create a new client liiks like:
|
The code to create a new client looks like:
|
||||||
|
|
||||||
var client = ldap.createClient({
|
var client = ldap.createClient({
|
||||||
url: 'ldap://127.0.0.1:1389'
|
url: 'ldap://127.0.0.1:1389'
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
---
|
||||||
|
title: ldapjs
|
||||||
|
brand: spartan
|
||||||
|
markdown2extras: wiki-tables
|
||||||
|
logo-color: green
|
||||||
|
logo-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
|
header-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
|
---
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
This documents the ldapjs DN API; this assumes that you are familiar with
|
||||||
|
LDAP, so if you're not, hit the [guide](http://ldapjs.org/guide.html) first.
|
||||||
|
|
||||||
|
DNs are LDAP distinguished names, and are composed of a set of RDNs (relative
|
||||||
|
distinguished names). [RFC2253](http://www.ietf.org/rfc/rfc2253.txt) has the
|
||||||
|
complete specification, but basically an RDN is an attribute value assertion
|
||||||
|
with `=` as the seperator, like: `cn=foo` where 'cn' is 'commonName' and 'foo'
|
||||||
|
is the value. You can have compound RDNs by using the `+` character:
|
||||||
|
`cn=foo+sn=bar`. As stated above, DNs are a set of RDNs, typically separated
|
||||||
|
with the `,` character, like: `cn=foo, ou=people, o=example`. This uniquely
|
||||||
|
identifies an entry in the tree, and is read "bottom up".
|
||||||
|
|
||||||
|
# parseDN(dnString)
|
||||||
|
|
||||||
|
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
|
||||||
|
ldapjs framework, but if you need it, it's there.
|
||||||
|
|
||||||
|
var parseDN = require('ldapjs').parseDN;
|
||||||
|
|
||||||
|
var dn = parseDN('cn=foo+sn=bar, ou=people, o=example');
|
||||||
|
console.log(dn.toString());
|
||||||
|
|
||||||
|
# DN
|
||||||
|
|
||||||
|
The DN object is largely what you'll be interacting with, since all the server
|
||||||
|
APIs are setup to give you a DN object.
|
||||||
|
|
||||||
|
## childOf(dn)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
server.add('o=example', function(req, res, next) {
|
||||||
|
if (req.dn.childOf('ou=people, o=example')) {
|
||||||
|
...
|
||||||
|
} else {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
## parentOf(dn)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
server.add('o=example', function(req, res, next) {
|
||||||
|
var dn = parseDN('ou=people, o=example');
|
||||||
|
if (dn.parentOf(req.dn)) {
|
||||||
|
...
|
||||||
|
} else {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
## equals(dn)
|
||||||
|
|
||||||
|
Returns a boolean indicating whether `this` is equivalent to the passed in `dn`
|
||||||
|
argument. `dn` can be a string or a DN.
|
||||||
|
|
||||||
|
server.add('o=example', function(req, res, next) {
|
||||||
|
if (req.dn.equals('cn=foo, ou=people, o=example')) {
|
||||||
|
...
|
||||||
|
} else {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
## parent()
|
||||||
|
|
||||||
|
Returns a DN object that is the direct parent of `this`. If there is no parent
|
||||||
|
this can return `null` (e.g. `parseDN('o=example').parent()` will return null).
|
||||||
|
|
||||||
|
## toString()
|
||||||
|
|
||||||
|
Returns the string representation of `this`.
|
||||||
|
|
||||||
|
server.add('o=example', function(req, res, next) {
|
||||||
|
console.log(req.dn.toString());
|
||||||
|
});
|
|
@ -0,0 +1,93 @@
|
||||||
|
---
|
||||||
|
title: ldapjs
|
||||||
|
brand: spartan
|
||||||
|
markdown2extras: wiki-tables
|
||||||
|
logo-color: green
|
||||||
|
logo-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
|
header-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
|
---
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
This documents the ldapjs Errors API; this assumes that you are familiar with
|
||||||
|
LDAP, so if you're not, hit the [guide](http://ldapjs.org/guide.html) first.
|
||||||
|
|
||||||
|
All errors in the ldapjs framework extend from an abstract error type called
|
||||||
|
`LDAPError`. In addition to the properties listed below, all errors will have
|
||||||
|
a `stack` property correctly set.
|
||||||
|
|
||||||
|
In general, you'll be using the errors in ldapjs like:
|
||||||
|
|
||||||
|
var ldap = require('ldapjs');
|
||||||
|
|
||||||
|
var db = {};
|
||||||
|
|
||||||
|
server.add('o=example', function(req, res, next) {
|
||||||
|
var parent = req.dn.parent();
|
||||||
|
if (parent) {
|
||||||
|
if (!db[parent.toString()])
|
||||||
|
return next(new ldap.NoSuchObjectError(parent.toString()));
|
||||||
|
}
|
||||||
|
if (db[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
|
||||||
|
return the appropriate LDAP error message, and stop the handler chain.
|
||||||
|
|
||||||
|
All errors will have the following properties:
|
||||||
|
|
||||||
|
## code
|
||||||
|
|
||||||
|
Returns the LDAP status code associated with this error.
|
||||||
|
|
||||||
|
## name
|
||||||
|
|
||||||
|
The name of this error.
|
||||||
|
|
||||||
|
## message
|
||||||
|
|
||||||
|
The message that will be returned to the client.
|
||||||
|
|
||||||
|
# Complete list of LDAPError subclasses
|
||||||
|
|
||||||
|
* OperationsError
|
||||||
|
* ProtocolError
|
||||||
|
* TimeLimitExceededError
|
||||||
|
* SizeLimitExceededError
|
||||||
|
* CompareFalseError
|
||||||
|
* CompareTrueError
|
||||||
|
* AuthMethodNotSupportedError
|
||||||
|
* StrongAuthRequiredError
|
||||||
|
* ReferralError
|
||||||
|
* AdminLimitExceededError
|
||||||
|
* UnavailableCriticalExtensionError
|
||||||
|
* ConfidentialityRequiredError
|
||||||
|
* SaslBindInProgressError
|
||||||
|
* NoSuchAttributeError
|
||||||
|
* UndefinedAttributeTypeError
|
||||||
|
* InappropriateMatchingError
|
||||||
|
* ConstraintViolationError
|
||||||
|
* AttributeOrValueExistsError
|
||||||
|
* InvalidAttriubteSyntaxError
|
||||||
|
* NoSuchObjectError
|
||||||
|
* AliasProblemError
|
||||||
|
* InvalidDnSyntaxError
|
||||||
|
* AliasDerefProblemError
|
||||||
|
* InappropriateAuthenticationError
|
||||||
|
* InvalidCredentialsError
|
||||||
|
* InsufficientAccessRightsError
|
||||||
|
* BusyError
|
||||||
|
* UnavailableError
|
||||||
|
* UnwillingToPerformError
|
||||||
|
* LoopDetectError
|
||||||
|
* NamingViolationError
|
||||||
|
* ObjectclassViolationError
|
||||||
|
* NotAllowedOnNonLeafError
|
||||||
|
* NotAllowedOnRdnError
|
||||||
|
* EntryAlreadyExistsError
|
||||||
|
* ObjectclassModsProhibitedError
|
||||||
|
* AffectsMultipleDsasError
|
||||||
|
* OtherError
|
140
docs/examples.md
140
docs/examples.md
|
@ -16,7 +16,7 @@ header-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ header-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
|
|
||||||
server.bind('cn=root', function(req, res, next) {
|
server.bind('cn=root', function(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();
|
||||||
|
@ -42,7 +42,7 @@ header-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
var dn = req.dn.toString();
|
var dn = req.dn.toString();
|
||||||
|
|
||||||
if (db[dn])
|
if (db[dn])
|
||||||
return next(new ldap.EntryAlreadyExistsError(dn));
|
return next(new ldap.EntryAlreadyExistsError(dn));
|
||||||
|
|
||||||
db[dn] = req.toObject().attributes;
|
db[dn] = req.toObject().attributes;
|
||||||
res.end();
|
res.end();
|
||||||
|
@ -52,13 +52,13 @@ header-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
server.bind(SUFFIX, function(req, res, next) {
|
server.bind(SUFFIX, function(req, res, next) {
|
||||||
var dn = req.dn.toString();
|
var dn = req.dn.toString();
|
||||||
if (!db[dn])
|
if (!db[dn])
|
||||||
return next(new ldap.NoSuchObjectError(dn));
|
return next(new ldap.NoSuchObjectError(dn));
|
||||||
|
|
||||||
if (!dn[dn].userpassword)
|
if (!dn[dn].userpassword)
|
||||||
return next(new ldap.NoSuchAttributeError('userPassword'));
|
return next(new ldap.NoSuchAttributeError('userPassword'));
|
||||||
|
|
||||||
if (db[dn].userpassword !== req.credentials)
|
if (db[dn].userpassword !== req.credentials)
|
||||||
return next(new ldap.InvalidCredentialsError());
|
return next(new ldap.InvalidCredentialsError());
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
return next();
|
return next();
|
||||||
|
@ -67,18 +67,18 @@ header-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
server.compare(SUFFIX, authorize, function(req, res, next) {
|
server.compare(SUFFIX, authorize, function(req, res, next) {
|
||||||
var dn = req.dn.toString();
|
var 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;
|
var matches = false;
|
||||||
var vals = db[dn][req.attribute];
|
var vals = db[dn][req.attribute];
|
||||||
for (var i = 0; i < vals.length; i++) {
|
for (var i = 0; i < vals.length; i++) {
|
||||||
if (vals[i] === req.value) {
|
if (vals[i] === req.value) {
|
||||||
matches = true;
|
matches = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.end(matches);
|
res.end(matches);
|
||||||
|
@ -88,7 +88,7 @@ header-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
server.del(SUFFIX, authorize, function(req, res, next) {
|
server.del(SUFFIX, authorize, function(req, res, next) {
|
||||||
var dn = req.dn.toString();
|
var dn = req.dn.toString();
|
||||||
if (!db[dn])
|
if (!db[dn])
|
||||||
return next(new ldap.NoSuchObjectError(dn));
|
return next(new ldap.NoSuchObjectError(dn));
|
||||||
|
|
||||||
delete db[dn];
|
delete db[dn];
|
||||||
|
|
||||||
|
@ -99,47 +99,47 @@ header-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
server.modify(SUFFIX, authorize, function(req, res, next) {
|
server.modify(SUFFIX, authorize, function(req, res, next) {
|
||||||
var dn = req.dn.toString();
|
var 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];
|
var entry = db[dn];
|
||||||
|
|
||||||
for (var i = 0; i < req.changes.length; i++) {
|
for (var 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':
|
||||||
if (!entry[mod.type])
|
if (!entry[mod.type])
|
||||||
return next(new ldap.NoSuchAttributeError(mod.type));
|
return next(new ldap.NoSuchAttributeError(mod.type));
|
||||||
|
|
||||||
if (!mod.vals || !mod.vals.length) {
|
if (!mod.vals || !mod.vals.length) {
|
||||||
delete entry[mod.type];
|
delete entry[mod.type];
|
||||||
} else {
|
} else {
|
||||||
entry[mod.type] = mod.vals;
|
entry[mod.type] = mod.vals;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'add':
|
case 'add':
|
||||||
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) {
|
mod.vals.forEach(function(v) {
|
||||||
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;
|
||||||
|
|
||||||
case 'delete':
|
case 'delete':
|
||||||
if (!entry[mod.type])
|
if (!entry[mod.type])
|
||||||
return next(new ldap.NoSuchAttributeError(mod.type));
|
return next(new ldap.NoSuchAttributeError(mod.type));
|
||||||
|
|
||||||
delete entry[mod.type];
|
delete entry[mod.type];
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
|
@ -149,50 +149,50 @@ header-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
server.search(SUFFIX, authorize, function(req, res, next) {
|
server.search(SUFFIX, authorize, function(req, res, next) {
|
||||||
var dn = req.dn.toString();
|
var dn = req.dn.toString();
|
||||||
if (!db[dn])
|
if (!db[dn])
|
||||||
return next(new ldap.NoSuchObjectError(dn));
|
return next(new ldap.NoSuchObjectError(dn));
|
||||||
|
|
||||||
var scopeCheck;
|
var scopeCheck;
|
||||||
|
|
||||||
switch (req.scope) {
|
switch (req.scope) {
|
||||||
case 'base':
|
case 'base':
|
||||||
if (req.filter.matches(db[dn])) {
|
if (req.filter.matches(db[dn])) {
|
||||||
res.send({
|
res.send({
|
||||||
dn: dn,
|
dn: dn,
|
||||||
attributes: db[dn]
|
attributes: db[dn]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
return next();
|
return next();
|
||||||
|
|
||||||
case 'one':
|
case 'one':
|
||||||
scopeCheck = function(k) {
|
scopeCheck = function(k) {
|
||||||
if (req.dn.equals(k))
|
if (req.dn.equals(k))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
var parent = ldap.parseDN(k).parent();
|
var 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 = function(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) {
|
Object.keys(db).forEach(function(key) {
|
||||||
if (!scopeCheck(key))
|
if (!scopeCheck(key))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (req.filter.matches(db[key])) {
|
if (req.filter.matches(db[key])) {
|
||||||
res.send({
|
res.send({
|
||||||
dn: key,
|
dn: key,
|
||||||
attributes: db[key]
|
attributes: db[key]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
|
|
|
@ -0,0 +1,295 @@
|
||||||
|
---
|
||||||
|
title: ldapjs
|
||||||
|
brand: spartan
|
||||||
|
markdown2extras: wiki-tables
|
||||||
|
logo-color: green
|
||||||
|
logo-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
|
header-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
|
---
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
This documents the ldapjs server API; this assumes that you are familiar with
|
||||||
|
LDAP, so if you're not, hit the [guide](http://ldapjs.org/guide.html) first.
|
||||||
|
|
||||||
|
LDAP search filters are really the backbone of LDAP search operations, and
|
||||||
|
ldapjs tries to get you in "easy" with them if your dataset is small, and also
|
||||||
|
lets you introspect them if you want to write a "query planner". For reference,
|
||||||
|
make sure to read over [RFC2254](http://www.ietf.org/rfc/rfc2254.txt), as this
|
||||||
|
explains the LDAPv3 text filter representation.
|
||||||
|
|
||||||
|
Basically, ldapjs gives you a distinct object type mapping to each filter that
|
||||||
|
is context-sensitive. However, _all_ filters have a `matches()` api on them, if
|
||||||
|
that's all you need. Most filters will have an `attribute` property on them,
|
||||||
|
since "simple" filters all operate on an attribute/value assertion. The
|
||||||
|
"complex" filters are really aggregations of other filters (i.e. 'and'), and so
|
||||||
|
these don't provide that property.
|
||||||
|
|
||||||
|
All Filters in the ldapjs framework extend from `Filter`, which wil have the
|
||||||
|
property `type` available; this will return a string name for the filter, and
|
||||||
|
will be one of:
|
||||||
|
|
||||||
|
* _equal:_ an `EqualityFilter`
|
||||||
|
* _present:_ a `PresenceFilter`
|
||||||
|
* _substring:_ a `SubstringFilter`
|
||||||
|
* _ge:_ a `GreaterThanEqualsFilter`
|
||||||
|
* _le:_ a `LessThanEqualsFilter`
|
||||||
|
* _and:_ an `AndFilter`
|
||||||
|
* _or:_ an `OrFilter`
|
||||||
|
* _not:_ a `NotFilter`
|
||||||
|
* _approx:_ an `ApproximateMatchFilter` (quasi-supported in ldapjs)
|
||||||
|
* _ext:_ an `ExtensibleMatchFilter` (not supported in ldapjs)
|
||||||
|
|
||||||
|
# parseFilter(filterString)
|
||||||
|
|
||||||
|
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.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
var parseFilter = require('ldapjs').parseFilter;
|
||||||
|
|
||||||
|
var f = parseFilter('(objectclass=*)');
|
||||||
|
|
||||||
|
Is a "simple" filter, and would just return a `PresenceFilter` object. However,
|
||||||
|
|
||||||
|
var f = parseFilter('(&(employeeType=manager)(l=Seattle))');
|
||||||
|
|
||||||
|
Would return an `AndFilter`, which would have a `filters` array of two
|
||||||
|
`EqualityFilter` objects.
|
||||||
|
|
||||||
|
Note that `parseFilter` will throw if an invalid string is passed in
|
||||||
|
(that is, a syntactically invalid string). All filter objects in th
|
||||||
|
|
||||||
|
# EqualityFilter
|
||||||
|
|
||||||
|
The equality filter is used to check exact matching of attribute/value
|
||||||
|
assertions. This object will have an `attribute` and `value` property, and the
|
||||||
|
`name` proerty will be `equal`.
|
||||||
|
|
||||||
|
The string syntax for an equality filter is `(attr=value)`.
|
||||||
|
|
||||||
|
The `matches()` method will return true IFF the passed in object has a
|
||||||
|
key matching `attribute` and a value matching `value`.
|
||||||
|
|
||||||
|
var f = new EqualityFilter({
|
||||||
|
attribute: 'cn',
|
||||||
|
value: 'foo'
|
||||||
|
});
|
||||||
|
|
||||||
|
f.matches({cn: 'foo'}); => true
|
||||||
|
f.matches({cn: 'bar'}); => false
|
||||||
|
|
||||||
|
Note that "strict" equality matching is used, and by default everything in
|
||||||
|
ldapjs (and LDAP) is a UTF-8 string. If you want comparison of numbers, or
|
||||||
|
something else, you'll need to use a middleware interceptor that transforms
|
||||||
|
values of objects.
|
||||||
|
|
||||||
|
# PresenceFilter
|
||||||
|
|
||||||
|
The presence filter is used to check if an object has an attribute at all, with
|
||||||
|
any value. This object will have an `attribute` property, and the `name`
|
||||||
|
property will be `present`.
|
||||||
|
|
||||||
|
The string syntax for a presence filter is `(attr=*)`.
|
||||||
|
|
||||||
|
The `matches()` method will return true IFF the passed in object has a
|
||||||
|
key matching `attribute`.
|
||||||
|
|
||||||
|
var f = new PresenceFilter({
|
||||||
|
attribute: 'cn'
|
||||||
|
});
|
||||||
|
|
||||||
|
f.matches({cn: 'foo'}); => true
|
||||||
|
f.matches({sn: 'foo'}); => false
|
||||||
|
|
||||||
|
# SubstringFilter
|
||||||
|
|
||||||
|
The substring filter is used to do wildcard matching of a string value. This
|
||||||
|
object will have an `attribute` property and then it will have an `initial`
|
||||||
|
property, which is the prefix match, an `any` which will be an array of strings
|
||||||
|
that are to be found _somewhere_ in the target string, and a `final` property,
|
||||||
|
which will be the suffix match of the string. `any` and `final` are both
|
||||||
|
optional. The `name` property will be `substring`.
|
||||||
|
|
||||||
|
The string syntax for a presence filter is `(attr=foo*bar*cat*dog)`, which would
|
||||||
|
map to:
|
||||||
|
{
|
||||||
|
initial: 'foo',
|
||||||
|
any: ['bar', 'cat'],
|
||||||
|
final: 'dog'
|
||||||
|
}
|
||||||
|
|
||||||
|
The `matches()` method will return true IFF the passed in object has a
|
||||||
|
key matching `attribute` and the "regex" matches the value
|
||||||
|
|
||||||
|
var f = new SubstringFilter({
|
||||||
|
attribute: 'cn',
|
||||||
|
initial: 'foo',
|
||||||
|
any: ['bar'],
|
||||||
|
final: 'baz'
|
||||||
|
});
|
||||||
|
|
||||||
|
f.matches({cn: 'foobigbardogbaz'}); => true
|
||||||
|
f.matches({sn: 'fobigbardogbaz'}); => false
|
||||||
|
|
||||||
|
# GreaterThanEqualsFilter
|
||||||
|
|
||||||
|
The ge filter is used to do comparisons and ordering based on the value type. As
|
||||||
|
mentioned elsewhere, by default everything in LDAP and ldapjs is a string, so
|
||||||
|
this filter's `matches()` would be using lexicographical ordering of strings.
|
||||||
|
If you wanted `>=` semantics over numeric values, you would need to add some
|
||||||
|
middleware to convert values before comparison (and the value of the filter).
|
||||||
|
Note that the ldapjs schema middleware will do this.
|
||||||
|
|
||||||
|
The GreaterThanEqualsFilter will have an `attribute` property, a `value`
|
||||||
|
property and the `name` property will be `ge`.
|
||||||
|
|
||||||
|
The string syntax for a ge filter is:
|
||||||
|
|
||||||
|
(cn>=foo)
|
||||||
|
|
||||||
|
The `matches()` method will return true IFF the passed in object has a
|
||||||
|
key matching `attribute` and the value is `>=` this filter's `value`.
|
||||||
|
|
||||||
|
var f = new GreaterThanEqualsFilter({
|
||||||
|
attribute: 'cn',
|
||||||
|
value: 'foo',
|
||||||
|
});
|
||||||
|
|
||||||
|
f.matches({cn: 'foobar'}); => true
|
||||||
|
f.matches({cn: 'abc'}); => false
|
||||||
|
|
||||||
|
# LessThanEqualsFilter
|
||||||
|
|
||||||
|
The le filter is used to do comparisons and ordering based on the value type. As
|
||||||
|
mentioned elsewhere, by default everything in LDAP and ldapjs is a string, so
|
||||||
|
this filter's `matches()` would be using lexicographical ordering of strings.
|
||||||
|
If you wanted `<=` semantics over numeric values, you would need to add some
|
||||||
|
middleware to convert values before comparison (and the value of the filter).
|
||||||
|
Note that the ldapjs schema middleware will do this.
|
||||||
|
|
||||||
|
The string syntax for a le filter is:
|
||||||
|
|
||||||
|
(cn<=foo)
|
||||||
|
|
||||||
|
The LessThanEqualsFilter will have an `attribute` property, a `value`
|
||||||
|
property and the `name` property will be `le`.
|
||||||
|
|
||||||
|
The `matches()` method will return true IFF the passed in object has a
|
||||||
|
key matching `attribute` and the value is `<=` this filter's `value`.
|
||||||
|
|
||||||
|
var f = new LessThanEqualsFilter({
|
||||||
|
attribute: 'cn',
|
||||||
|
value: 'foo',
|
||||||
|
});
|
||||||
|
|
||||||
|
f.matches({cn: 'abc'}); => true
|
||||||
|
f.matches({cn: 'foo'}); => false
|
||||||
|
|
||||||
|
# AndFilter
|
||||||
|
|
||||||
|
The and filter is a complex filter that simply contains "child" filters. The
|
||||||
|
object will have a `filters` property which is an array of `Filter` objects. The
|
||||||
|
`name` property will be `and`.
|
||||||
|
|
||||||
|
The string syntax for an and filter is (assuming below we're and'ing two
|
||||||
|
equality filters):
|
||||||
|
|
||||||
|
(&(cn=foo)(sn=bar))
|
||||||
|
|
||||||
|
The `matches()` method will return true IFF the passed in object matches all
|
||||||
|
the filters in the `filters` array.
|
||||||
|
|
||||||
|
var f = new AndFilter({
|
||||||
|
filters: [
|
||||||
|
new EqualityFilter({
|
||||||
|
attribute: 'cn',
|
||||||
|
value: 'foo'
|
||||||
|
}),
|
||||||
|
new EqualityFilter({
|
||||||
|
attribute: 'sn',
|
||||||
|
value: 'bar'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
f.matches({cn: 'foo', sn: 'bar'}); => true
|
||||||
|
f.matches({cn: 'foo', sn: 'baz'}); => false
|
||||||
|
|
||||||
|
# OrFilter
|
||||||
|
|
||||||
|
The or filter is a complex filter that simply contains "child" filters. The
|
||||||
|
object will have a `filters` property which is an array of `Filter` objects. The
|
||||||
|
`name` property will be `or`.
|
||||||
|
|
||||||
|
The string syntax for an or filter is (assuming below we're or'ing two
|
||||||
|
equality filters):
|
||||||
|
|
||||||
|
(|(cn=foo)(sn=bar))
|
||||||
|
|
||||||
|
The `matches()` method will return true IFF the passed in object matches *any*
|
||||||
|
of the filters in the `filters` array.
|
||||||
|
|
||||||
|
var f = new OrFilter({
|
||||||
|
filters: [
|
||||||
|
new EqualityFilter({
|
||||||
|
attribute: 'cn',
|
||||||
|
value: 'foo'
|
||||||
|
}),
|
||||||
|
new EqualityFilter({
|
||||||
|
attribute: 'sn',
|
||||||
|
value: 'bar'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
f.matches({cn: 'foo', sn: 'baz'}); => true
|
||||||
|
f.matches({cn: 'bar', sn: 'baz'}); => false
|
||||||
|
|
||||||
|
# NotFilter
|
||||||
|
|
||||||
|
The not filter is a complex filter that contains a single "child" filter. The
|
||||||
|
object will have a `filter` property which is an instance of a `Filter` object.
|
||||||
|
The `name` property will be `not`.
|
||||||
|
|
||||||
|
The string syntax for a not filter is (assuming below we're not'ing an
|
||||||
|
equality filter):
|
||||||
|
|
||||||
|
(!(cn=foo))
|
||||||
|
|
||||||
|
The `matches()` method will return true IFF the passed in object does not match
|
||||||
|
the filter in the `filter` property.
|
||||||
|
|
||||||
|
var f = new NotFilter({
|
||||||
|
filter: new EqualityFilter({
|
||||||
|
attribute: 'cn',
|
||||||
|
value: 'foo'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
f.matches({cn: 'bar'}); => true
|
||||||
|
f.matches({cn: 'foo'}); => false
|
||||||
|
|
||||||
|
# ApproximateFilter
|
||||||
|
|
||||||
|
The approximate filter is used to check "approximate" matching of
|
||||||
|
attribute/value assertions. This object will have an `attribute` and
|
||||||
|
`value` property, and the `name` proerty will be `approx`.
|
||||||
|
|
||||||
|
As a side point, this is a useless filter. It's really only here if you have
|
||||||
|
some whacky client that's sending this. It just does an exact match (which
|
||||||
|
is what ActiveDirectory does too).
|
||||||
|
|
||||||
|
The string syntax for an equality filter is `(attr~=value)`.
|
||||||
|
|
||||||
|
The `matches()` method will return true IFF the passed in object has a
|
||||||
|
key matching `attribute` and a value exactly matching `value`.
|
||||||
|
|
||||||
|
var f = new ApproximateFilter({
|
||||||
|
attribute: 'cn',
|
||||||
|
value: 'foo'
|
||||||
|
});
|
||||||
|
|
||||||
|
f.matches({cn: 'foo'}); => true
|
||||||
|
f.matches({cn: 'bar'}); => false
|
||||||
|
|
|
@ -0,0 +1,562 @@
|
||||||
|
---
|
||||||
|
title: ldapjs
|
||||||
|
brand: spartan
|
||||||
|
markdown2extras: wiki-tables
|
||||||
|
logo-color: green
|
||||||
|
logo-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
|
header-font-family: google:Aldrich, Verdana, sans-serif
|
||||||
|
---
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
This documents the ldapjs server API; this assumes that you are familiar with
|
||||||
|
LDAP, so if you're not, hit the [guide](http://ldapjs.org/guide.html) first.
|
||||||
|
|
||||||
|
# Create a server
|
||||||
|
|
||||||
|
The code to create a new server looks like:
|
||||||
|
|
||||||
|
var server = ldap.createServer();
|
||||||
|
|
||||||
|
Full list of options:
|
||||||
|
|
||||||
|
* _log4js:_ You can optionally pass in a log4js instance that the client will
|
||||||
|
get a logger from. You'll need to set the level to `TRACE` To get any output
|
||||||
|
from the client.
|
||||||
|
* _certificate:_ A PEM-encoded X.509 certificate; will cause this server to
|
||||||
|
run in TLS mode.
|
||||||
|
* _key:_ A PEM-encoded private key that corresponds to _certificate_ for SSL.
|
||||||
|
|
||||||
|
## Properties on server
|
||||||
|
|
||||||
|
### maxConnections
|
||||||
|
|
||||||
|
Set this property to reject connections when the server's connection count gets
|
||||||
|
high.
|
||||||
|
|
||||||
|
### connections (getter only)
|
||||||
|
|
||||||
|
The number of concurrent connections on the server.
|
||||||
|
|
||||||
|
### url
|
||||||
|
|
||||||
|
Returns the fully qualified URL this server is listening on. For example:
|
||||||
|
`ldaps://10.1.2.3:1636`. If you haven't yet called `listen`, it will always
|
||||||
|
return `ldap://localhost:389`.
|
||||||
|
|
||||||
|
### Event: 'close'
|
||||||
|
`function() {}`
|
||||||
|
|
||||||
|
Emitted when the server closes.
|
||||||
|
|
||||||
|
## Listening for requests
|
||||||
|
|
||||||
|
The LDAP server API wraps up and mirrors the node
|
||||||
|
[listen](http://nodejs.org/docs/v0.4.11/api/net.html#server.listen) family of
|
||||||
|
APIs.
|
||||||
|
|
||||||
|
After calling `listen`, the property `url` on the server object itself will be
|
||||||
|
available.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
server.listen(389, '127.0.0.1', function() {
|
||||||
|
console.log(LDAP server listening at: ' + server.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
### Port and Host
|
||||||
|
`listen(port, [host], [callback])`
|
||||||
|
|
||||||
|
Begin accepting connections on the specified port and host. If the host is
|
||||||
|
omitted, the server will accept connections directed to any IPv4 address
|
||||||
|
(INADDR_ANY).
|
||||||
|
|
||||||
|
This function is asynchronous. The last parameter callback will be called when
|
||||||
|
the server has been bound.
|
||||||
|
|
||||||
|
### Unix Domain Socket
|
||||||
|
`listen(path, [callback])`
|
||||||
|
|
||||||
|
Start a UNIX socket server listening for connections on the given path.
|
||||||
|
|
||||||
|
This function is asynchronous. The last parameter callback will be called when
|
||||||
|
the server has been bound.
|
||||||
|
|
||||||
|
### File descriptor
|
||||||
|
`listenFD(fd)`
|
||||||
|
|
||||||
|
Start a server listening for connections on the given file descriptor.
|
||||||
|
|
||||||
|
This file descriptor must have already had the `bind(2)` and `listen(2)` system
|
||||||
|
calls invoked on it. Additionally, it must be set non-blocking; try
|
||||||
|
`fcntl(fd, F_SETFL, O_NONBLOCK)`.
|
||||||
|
|
||||||
|
# Routes
|
||||||
|
|
||||||
|
The LDAP server API is meant to be the LDAP-equivalent of the express/sinatra
|
||||||
|
paradigm of programming. Essentially every method is of the form
|
||||||
|
`OP(req, res, next)` where OP is one of bind, add, del, etc. You can chain
|
||||||
|
handlers together by calling `next()` and ordering your functions in the
|
||||||
|
definition of the route. For example:
|
||||||
|
|
||||||
|
function authorize(req, res, next) {
|
||||||
|
if (!req.connection.ldap.bindDN.equals('cn=root'))
|
||||||
|
return next(new ldap.InsufficientAccessRightsError());
|
||||||
|
|
||||||
|
return 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
|
||||||
|
to a DB-like entity, in that it also has an API where you can pass in a
|
||||||
|
'backend' object. This is necessary if there are persistent connection pools,
|
||||||
|
caching, etc. that need to be placed in an object.
|
||||||
|
|
||||||
|
For example [ldapjs-riak](https://github.com/mcavage/node-ldapjs-riak) is a
|
||||||
|
complete implementation of the LDAP protocol over
|
||||||
|
[Riak](http://www.basho.com/products_riak_overview.php). Getting an LDAP
|
||||||
|
server up with riak looks like:
|
||||||
|
|
||||||
|
var ldap = require('ldapjs');
|
||||||
|
var ldapRiak = require('ldapjs-riak');
|
||||||
|
|
||||||
|
var server = ldap.createServer();
|
||||||
|
var backend = ldapRiak.createBackend({
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 8098,
|
||||||
|
"bucket": "example",
|
||||||
|
"indexes": ["l", "cn"],
|
||||||
|
"uniqueIndexes": ["uid"],
|
||||||
|
"numConnections": 5
|
||||||
|
});
|
||||||
|
|
||||||
|
server.add("o=example",
|
||||||
|
backend,
|
||||||
|
backend.add());
|
||||||
|
...
|
||||||
|
|
||||||
|
Basically, 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
|
||||||
|
backend object, if applicable. After that you can pass in an arbitrary
|
||||||
|
combination of functions in the form `f(req, res, next)` or Arrays of functions
|
||||||
|
of the same signature (ldapjs will unroll them).
|
||||||
|
|
||||||
|
Unlike HTTP, LDAP operations do not have a heterogenous format, so each
|
||||||
|
operation in the rest of the document includes documentation for the
|
||||||
|
request/response objects appropriate to that operation type.
|
||||||
|
|
||||||
|
## Common Request Elements
|
||||||
|
|
||||||
|
All request objects has the `dn` getter on it, which is "context-sensitive"
|
||||||
|
and returns the point in the tree that the operation wants to operate on. The
|
||||||
|
LDAP protocol itself sadly doesn't define operations this way, and has a unique
|
||||||
|
name for just about every op. So, ldapjs calls it `dn`. The DN object itself
|
||||||
|
is documented at [DN](/dn.html).
|
||||||
|
|
||||||
|
All requests have an optional array of `Control` objects. `Control` will have
|
||||||
|
the properties `type` (string), `criticality` (boolean), and optionally a string
|
||||||
|
`value`.
|
||||||
|
|
||||||
|
All request objects will have a `connection` object, which is the `net.Socket`
|
||||||
|
associated to this request. Off the `connection` object is an `ldap` object.
|
||||||
|
The most important property to pay attention to there is the `bindDN` property
|
||||||
|
which will be an instance of an `ldap.DN` object. This is what the client
|
||||||
|
authenticated as on this connection. If the client didn't bind, then a DN object
|
||||||
|
will be there defaulted to `cn=anonymous`.
|
||||||
|
|
||||||
|
Additionally, request will have a `logId` parameter you can use to uniquely
|
||||||
|
identify the request/connection pair in logs (includes the LDAP messageID).
|
||||||
|
|
||||||
|
## Common Response Elements
|
||||||
|
|
||||||
|
All response objects will have an `end` method on them. By default, calling
|
||||||
|
`res.end()` with no arguments will return SUCCESS (0x00) to the client
|
||||||
|
(with the exception of `compare` which will return COMPARE_TRUE (0x06)). You
|
||||||
|
can pass in a status code to the `end()` method to return an alternate status
|
||||||
|
code.
|
||||||
|
|
||||||
|
However, it's more common/easier to use the `return next(new LDAPError())`
|
||||||
|
pattern, since ldapjs will fill in the extra LDAPResult fields like matchedDN
|
||||||
|
and error message for you.
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
ldapjs includes an exception hierarchy that directly corresponds to the RFC list
|
||||||
|
of error codes. The complete list is documented in [errors](/errors.html). But
|
||||||
|
the paradigm is something defined like CONSTRAINT_VIOLATION in the RFC would be
|
||||||
|
`ConstraintViolationError` in ldapjs. Upon calling `next(new LDAPError())`,
|
||||||
|
ldapjs will _stop_ calling your handler chain. For example:
|
||||||
|
|
||||||
|
server.search('o=example',
|
||||||
|
function(req, res, next) { return next(); },
|
||||||
|
function(req, res, next) { return next(new ldap.OperationsError()); },
|
||||||
|
function(req, res, next) { res.end(); }
|
||||||
|
);
|
||||||
|
|
||||||
|
In the code snipped above, the third handler would never get invoked.
|
||||||
|
|
||||||
|
# Bind
|
||||||
|
|
||||||
|
Adds a mount in the tree to perform LDAP binds with. Example:
|
||||||
|
|
||||||
|
server.bind('ou=people, o=example', function(req, res, next) {
|
||||||
|
console.log('bind DN: ' + req.dn.toString());
|
||||||
|
console.log('bind PW: ' + req.credentials);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
## BindRequest
|
||||||
|
|
||||||
|
BindRequest objects have the following properties:
|
||||||
|
|
||||||
|
### version
|
||||||
|
|
||||||
|
The LDAP protocol version the client is requesting to run this connection on.
|
||||||
|
Note that ldapjs only supports LDAP version 3.
|
||||||
|
|
||||||
|
### name
|
||||||
|
|
||||||
|
The DN the client is attempting to bind as (note this is the same as the `dn`
|
||||||
|
property).
|
||||||
|
|
||||||
|
### authentication
|
||||||
|
|
||||||
|
The method of authentication. Right now only `simple` is supported.
|
||||||
|
|
||||||
|
### credentials
|
||||||
|
|
||||||
|
The credentials to go with the `name/authentication` pair. For `simple`, this
|
||||||
|
will be the plain-text password.
|
||||||
|
|
||||||
|
## BindResponse
|
||||||
|
|
||||||
|
No extra methods above an `LDAPResult` API call.
|
||||||
|
|
||||||
|
# Add
|
||||||
|
|
||||||
|
Adds a mount in the tree to perform LDAP adds with. Example:
|
||||||
|
|
||||||
|
server.add('ou=people, o=example', function(req, res, next) {
|
||||||
|
console.log('DN: ' + req.dn.toString());
|
||||||
|
console.log('Entry attributes: ' + req.toObject().attributes);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
## AddRequest
|
||||||
|
|
||||||
|
AddRequest objects have the following properties:
|
||||||
|
|
||||||
|
### entry
|
||||||
|
|
||||||
|
The DN the client is attempting to add (note this is the same as the `dn`
|
||||||
|
property).
|
||||||
|
|
||||||
|
### attributes
|
||||||
|
|
||||||
|
The set of attributes in this entry. Note that this will be an array of
|
||||||
|
`Attribute` objects (which have a type and an array of values). This directly
|
||||||
|
maps to how the request came in off the wire. It's likely you'll want to use
|
||||||
|
`toObject()` and iterate that way, since that will transform an AddRequest into
|
||||||
|
a standard JavaScript object.
|
||||||
|
|
||||||
|
### toObject()
|
||||||
|
|
||||||
|
This operation will return a plain JavaScript object from the request that looks
|
||||||
|
like:
|
||||||
|
|
||||||
|
{
|
||||||
|
dn: 'cn=foo, o=example', // string, not DN object
|
||||||
|
attributes: {
|
||||||
|
cn: ['foo'],
|
||||||
|
sn: ['bar'],
|
||||||
|
objectclass: ['person', 'top']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## AddResponse
|
||||||
|
|
||||||
|
No extra methods above an `LDAPResult` API call.
|
||||||
|
|
||||||
|
# Search
|
||||||
|
|
||||||
|
Adds a handler for the LDAP search operation. Example:
|
||||||
|
|
||||||
|
server.search('o=example', function(req, res, next) {
|
||||||
|
console.log('base object: ' + req.dn.toString());
|
||||||
|
console.log('scope: ' + req.scope);
|
||||||
|
console.log('filter: ' + req.filter.toString());
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
this.derefAliases = options.derefAliases || Protocol.NEVER_DEREF_ALIASES;
|
||||||
|
this.sizeLimit = options.sizeLimit || 0;
|
||||||
|
this.timeLimit = options.timeLimit || 00;
|
||||||
|
this.typesOnly = options.typesOnly || false;
|
||||||
|
this.filter = options.filter || null;
|
||||||
|
this.attributes = options.attributes ? options.attributes.slice(0) : [];
|
||||||
|
|
||||||
|
## SearchRequest
|
||||||
|
|
||||||
|
SearchRequest objects have the following properties:
|
||||||
|
|
||||||
|
### baseObject
|
||||||
|
|
||||||
|
The DN the client is attempting to start the search at (equivalent to `dn`).
|
||||||
|
|
||||||
|
### scope
|
||||||
|
|
||||||
|
(string) one of:
|
||||||
|
|
||||||
|
* base
|
||||||
|
* one
|
||||||
|
* sub
|
||||||
|
|
||||||
|
### derefAliases
|
||||||
|
|
||||||
|
Will be an integer (defined in the LDAP protocol). Defaults to '0' (meaning
|
||||||
|
never deref).
|
||||||
|
|
||||||
|
### sizeLimit
|
||||||
|
|
||||||
|
The number of entries to return. Defaults to '0' (unlimited). ldapjs doesn't
|
||||||
|
currently automatically enforce this, but probably will at some point.
|
||||||
|
|
||||||
|
### timeLimit
|
||||||
|
|
||||||
|
Maximum amount of time the server should take in sending search entries.
|
||||||
|
Defaults to '0' (unlimited).
|
||||||
|
|
||||||
|
### typesOnly
|
||||||
|
|
||||||
|
Whether to return only the names of attributes, and not the values. Defaults to
|
||||||
|
false. Note that ldapjs will take care of this for you.
|
||||||
|
|
||||||
|
### filter
|
||||||
|
|
||||||
|
The [filter](/filters.html) object that the client requested. Notably this has
|
||||||
|
a `matches()` api on it that you can leverage. For an example of introspecting
|
||||||
|
a filter, take a look at the ldapjs-riak source.
|
||||||
|
|
||||||
|
### attributes
|
||||||
|
|
||||||
|
An optional list of attributes to restrict the returned result sets to. ldapjs
|
||||||
|
will automatically handle this for you.
|
||||||
|
|
||||||
|
## SearchResponse
|
||||||
|
|
||||||
|
### send(entry)
|
||||||
|
|
||||||
|
Allows you to send a `SearchEntry` object. Note that you do not need to
|
||||||
|
explicitly pass in a `SearchEntry` object, and can instead just send a plain
|
||||||
|
JavaScript object that matches the format used from `AddRequest.toObject()`.
|
||||||
|
Example:
|
||||||
|
|
||||||
|
server.search('o=example', function(req, res, next) {
|
||||||
|
var obj = {
|
||||||
|
dn: 'o=example',
|
||||||
|
attributes: {
|
||||||
|
objectclass: ['top', 'organization'],
|
||||||
|
o: ['example']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (req.filter.matches(obj))
|
||||||
|
res.send(obj)
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
# modify
|
||||||
|
|
||||||
|
Allows you to handle an LDAP modify operation. Example:
|
||||||
|
|
||||||
|
server.modify('o=example', function(req, res, next) {
|
||||||
|
console.log('DN: ' + req.dn.toString());
|
||||||
|
console.log('changes:');
|
||||||
|
req.changes.forEach(function(c) {
|
||||||
|
console.log(' operation: ' + c.operation);
|
||||||
|
console.log(' modification: ' + c.modification.toString());
|
||||||
|
});
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
## ModifyRequest
|
||||||
|
|
||||||
|
ModifyRequest objects have the following properties:
|
||||||
|
|
||||||
|
### object
|
||||||
|
|
||||||
|
The DN the client is attempting to update (note this is the same as the `dn`
|
||||||
|
property).
|
||||||
|
|
||||||
|
### changes
|
||||||
|
|
||||||
|
An array of `Change` objects the client is attempting to perform. See below for
|
||||||
|
details on the `Change` object.
|
||||||
|
|
||||||
|
## Change
|
||||||
|
|
||||||
|
The Change object will have the following properties:
|
||||||
|
|
||||||
|
### operation
|
||||||
|
|
||||||
|
A string, and will be one of: 'add', 'delete', 'replace'.
|
||||||
|
|
||||||
|
### modification
|
||||||
|
|
||||||
|
Will be an `Attribute` object, which will have a 'type' (string) field, and
|
||||||
|
'vals', which will be an array of string values.
|
||||||
|
|
||||||
|
## ModifyResponse
|
||||||
|
|
||||||
|
No extra methods above an `LDAPResult` API call.
|
||||||
|
|
||||||
|
# del
|
||||||
|
|
||||||
|
Allows you to handle an LDAP delete operation. Example:
|
||||||
|
|
||||||
|
server.delete('o=example', function(req, res, next) {
|
||||||
|
console.log('DN: ' + req.dn.toString());
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
## DeleteRequest
|
||||||
|
|
||||||
|
### entry
|
||||||
|
|
||||||
|
The DN the client is attempting to delete (note this is the same as the `dn`
|
||||||
|
property).
|
||||||
|
|
||||||
|
## DeleteResponse
|
||||||
|
|
||||||
|
No extra methods above an `LDAPResult` API call.
|
||||||
|
|
||||||
|
# compare
|
||||||
|
|
||||||
|
Allows you to handle an LDAP compare operation. Example:
|
||||||
|
|
||||||
|
server.compare('o=example', function(req, res, next) {
|
||||||
|
console.log('DN: ' + req.dn.toString());
|
||||||
|
console.log('attribute name: ' + req.attribute);
|
||||||
|
console.log('attribute value: ' + req.value);
|
||||||
|
res.end(req.value === 'foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
## CompareRequest
|
||||||
|
|
||||||
|
### entry
|
||||||
|
|
||||||
|
The DN the client is attempting to compare (note this is the same as the `dn`
|
||||||
|
property).
|
||||||
|
|
||||||
|
### attribute
|
||||||
|
|
||||||
|
Will be the string name of the attribute to compare values of.
|
||||||
|
|
||||||
|
### value
|
||||||
|
|
||||||
|
The string value of the attribute to compare.
|
||||||
|
|
||||||
|
## CompareResponse
|
||||||
|
|
||||||
|
The `end()` method for compare takes a boolean, as opposed to a numeric code
|
||||||
|
(note you can still pass in a numeric LDAP status code if you want). Beyond
|
||||||
|
that, there are no extra methods above an `LDAPResult` API call.
|
||||||
|
|
||||||
|
# modifyDN
|
||||||
|
|
||||||
|
Allows you to handle an LDAP modifyDN operation. Example:
|
||||||
|
|
||||||
|
server.modifyDN('o=example', function(req, res, next) {
|
||||||
|
console.log('DN: ' + req.dn.toString());
|
||||||
|
console.log('new RDN: ' + req.newRdn.toString());
|
||||||
|
console.log('deleteOldRDN: ' + req.deleteOldRdn);
|
||||||
|
console.log('new superior: ' +
|
||||||
|
(req.newSuperior ? req.newSuperior.toString() : ''));
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
## ModifyDNRequest
|
||||||
|
|
||||||
|
### entry
|
||||||
|
|
||||||
|
The DN the client is attempting to rename (note this is the same as the `dn`
|
||||||
|
property).
|
||||||
|
|
||||||
|
### newRdn
|
||||||
|
|
||||||
|
The leaf RDN the client wants to rename this entry to. This will be a DN object.
|
||||||
|
|
||||||
|
### deleteOldRdn
|
||||||
|
|
||||||
|
boolean. Whether or not to delete the old RDN (i.e., rename vs copy). Defaults
|
||||||
|
to true.
|
||||||
|
|
||||||
|
### newSuperior
|
||||||
|
|
||||||
|
Optional (DN). If the modifyDN operation wishes to relocate the entry in the
|
||||||
|
tree, the newSuperior field will contain the new parent.
|
||||||
|
|
||||||
|
## ModifyDNResponse
|
||||||
|
|
||||||
|
No extra methods above an `LDAPResult` API call.
|
||||||
|
|
||||||
|
# exop
|
||||||
|
|
||||||
|
Allows you to handle an LDAP extended operation. Extended operations are pretty
|
||||||
|
much arbitrary extensions, by definition. Typically the extended 'name' is an
|
||||||
|
OID, but ldapjs makes no such restrictions; it just needs to be a string. Note
|
||||||
|
that 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.
|
||||||
|
Example:
|
||||||
|
|
||||||
|
// LDAP whoami
|
||||||
|
server.exop('1.3.6.1.4.1.4203.1.11.3', function(req, res, next) {
|
||||||
|
console.log('name: ' + req.name);
|
||||||
|
console.log('value: ' + req.value);
|
||||||
|
res.value = 'u:xxyyz@EXAMPLE.NET';
|
||||||
|
res.end();
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
|
## ExtendedRequest
|
||||||
|
|
||||||
|
### name
|
||||||
|
|
||||||
|
String. Will always be a match to the route-defined name. But the client
|
||||||
|
includes this in their requests.
|
||||||
|
|
||||||
|
### value
|
||||||
|
|
||||||
|
Optional string. The arbitrary blob the client sends for this extended
|
||||||
|
operation.
|
||||||
|
|
||||||
|
## ExtendedResponse
|
||||||
|
|
||||||
|
### name
|
||||||
|
|
||||||
|
The name of the extended operation. ldapjs will automatically set this.
|
||||||
|
|
||||||
|
### value
|
||||||
|
|
||||||
|
The arbitrary (string) value to send back as part of the response.
|
||||||
|
|
||||||
|
# unbind
|
||||||
|
|
||||||
|
ldapjs by default provides an unbind handler that just disconnects the client
|
||||||
|
and cleans up any internals (in ldapjs core). You can override this handler
|
||||||
|
if you need to clean up any items in your backend, for example.
|
||||||
|
|
||||||
|
server.unbind(function(req, res, next) {
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
ultimately calls `net.Socket.end()` for you. There are no properties available
|
||||||
|
on either the request or response objects, except of course for `end()` on the
|
||||||
|
response.
|
|
@ -44,7 +44,7 @@ function ModifyDNRequest(options) {
|
||||||
|
|
||||||
this.entry = options.entry || null;
|
this.entry = options.entry || null;
|
||||||
this.newRdn = options.newRdn || null;
|
this.newRdn = options.newRdn || null;
|
||||||
this.deleteOldRdn = options.deleteOldRdn || false;
|
this.deleteOldRdn = options.deleteOldRdn || true;
|
||||||
this.newSuperior = options.newSuperior || null;
|
this.newSuperior = options.newSuperior || null;
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
|
@ -71,7 +71,7 @@ function SearchRequest(options) {
|
||||||
this.scope = options.scope || 'base';
|
this.scope = options.scope || 'base';
|
||||||
this.derefAliases = options.derefAliases || Protocol.NEVER_DEREF_ALIASES;
|
this.derefAliases = options.derefAliases || Protocol.NEVER_DEREF_ALIASES;
|
||||||
this.sizeLimit = options.sizeLimit || 0;
|
this.sizeLimit = options.sizeLimit || 0;
|
||||||
this.timeLimit = options.timeLimit || 00;
|
this.timeLimit = options.timeLimit || 0;
|
||||||
this.typesOnly = options.typesOnly || false;
|
this.typesOnly = options.typesOnly || false;
|
||||||
this.filter = options.filter || null;
|
this.filter = options.filter || null;
|
||||||
this.attributes = options.attributes ? options.attributes.slice(0) : [];
|
this.attributes = options.attributes ? options.attributes.slice(0) : [];
|
||||||
|
|
|
@ -8,6 +8,7 @@ var asn1 = require('asn1');
|
||||||
var LDAPMessage = require('./message');
|
var LDAPMessage = require('./message');
|
||||||
var LDAPResult = require('./result');
|
var LDAPResult = require('./result');
|
||||||
|
|
||||||
|
var DN = require('../dn').DN;
|
||||||
var Protocol = require('../protocol');
|
var Protocol = require('../protocol');
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ function UnbindRequest(options) {
|
||||||
LDAPMessage.call(this, options);
|
LDAPMessage.call(this, options);
|
||||||
|
|
||||||
this.__defineGetter__('type', function() { return 'UnbindRequest'; });
|
this.__defineGetter__('type', function() { return 'UnbindRequest'; });
|
||||||
this.__defineGetter__('_dn', function() { return ''; });
|
this.__defineGetter__('_dn', function() { return new DN([{}]); });
|
||||||
}
|
}
|
||||||
util.inherits(UnbindRequest, LDAPMessage);
|
util.inherits(UnbindRequest, LDAPMessage);
|
||||||
module.exports = UnbindRequest;
|
module.exports = UnbindRequest;
|
||||||
|
|
|
@ -237,7 +237,7 @@ function Server(options) {
|
||||||
});
|
});
|
||||||
|
|
||||||
c.ldap.__defineGetter__('bindDN', function() {
|
c.ldap.__defineGetter__('bindDN', function() {
|
||||||
return c.ldap._bindDN || 'cn=anonymous';
|
return c.ldap._bindDN || new DN([{cn: 'anonymous'}]);
|
||||||
});
|
});
|
||||||
c.ldap.__defineSetter__('bindDN', function(val) {
|
c.ldap.__defineSetter__('bindDN', function(val) {
|
||||||
if (!(val instanceof DN))
|
if (!(val instanceof DN))
|
||||||
|
|
Loading…
Reference in New Issue