Doc guide cleanup

This commit is contained in:
Mark Cavage 2011-08-24 21:36:48 -07:00
parent cffaab7730
commit d9b6a07be9
6 changed files with 170 additions and 139 deletions

View File

@ -378,3 +378,25 @@ a#homelink:hover {
top: 0; top: 0;
right: 0px; right: 0px;
} }
#indextagline {
font-size: 24px;
font-style: italic;
text-align: center;
color: green;
}
a#indextaglink:link {
color: #008000;
text-decoration: none;
}
a#indextaglink:visited {
color: #008000;
text-decoration: none;
}
a#indextaglink:active {
color: #008000;
text-decoration: none;
}
a#indextaglink:hover {
color: #008000;
text-decoration: none;
}

View File

@ -1,6 +1,5 @@
--- ---
title: ldapjs title: ldapjs
brand: spartan
markdown2extras: wiki-tables markdown2extras: wiki-tables
logo-color: green logo-color: green
logo-font-family: google:Aldrich, Verdana, sans-serif logo-font-family: google:Aldrich, Verdana, sans-serif

View File

@ -1,6 +1,5 @@
--- ---
title: ldapjs title: ldapjs
brand: spartan
markdown2extras: wiki-tables markdown2extras: wiki-tables
logo-color: green logo-color: green
logo-font-family: google:Aldrich, Verdana, sans-serif logo-font-family: google:Aldrich, Verdana, sans-serif
@ -18,8 +17,8 @@ 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 make sure to read over [RFC2254](http://www.ietf.org/rfc/rfc2254.txt), as this
explains the LDAPv3 text filter representation. explains the LDAPv3 text filter representation.
Basically, ldapjs gives you a distinct object type mapping to each filter that ldapjs gives you a distinct object type mapping to each filter that is
is context-sensitive. However, _all_ filters have a `matches()` api on them, if context-sensitive. However, _all_ filters have a `matches()` method on them, if
that's all you need. Most filters will have an `attribute` property on them, 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 since "simple" filters all operate on an attribute/value assertion. The
"complex" filters are really aggregations of other filters (i.e. 'and'), and so "complex" filters are really aggregations of other filters (i.e. 'and'), and so
@ -57,8 +56,8 @@ Is a "simple" filter, and would just return a `PresenceFilter` object. However,
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.
Note that `parseFilter` will throw if an invalid string is passed in `parseFilter` will throw if an invalid string is passed in (that is, a
(that is, a syntactically invalid string). All filter objects in th syntactically invalid string). All filter objects in th
# EqualityFilter # EqualityFilter
@ -79,10 +78,10 @@ key matching `attribute` and a value matching `value`.
f.matches({cn: 'foo'}); => true f.matches({cn: 'foo'}); => true
f.matches({cn: 'bar'}); => false f.matches({cn: 'bar'}); => false
Note that "strict" equality matching is used, and by default everything in Equality matching uses "strict" type JavaScript comparison, and by default
ldapjs (and LDAP) is a UTF-8 string. If you want comparison of numbers, or everything in ldapjs (and LDAP) is a UTF-8 string. If you want comparison
something else, you'll need to use a middleware interceptor that transforms of numbers, or something else, you'll need to use a middleware interceptor
values of objects. that transforms values of objects.
# PresenceFilter # PresenceFilter
@ -113,6 +112,7 @@ 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:
{ {
initial: 'foo', initial: 'foo',
any: ['bar', 'cat'], any: ['bar', 'cat'],

View File

@ -10,26 +10,27 @@ header-font-family: google:Aldrich, Verdana, sans-serif
# This guide # This guide
This guide was written assuming that you (1) don't know anything about ldapjs, This guide was written assuming that you (1) don't know anything about ldapjs,
and perhaps more importantly (2) know little if anything about LDAP. If you're and perhaps more importantly (2) know little, if anything about LDAP. If you're
already an LDAP whiz, please don't read this and feel it's condescending. Most already an LDAP whiz, please don't read this and feel it's condescending. Most
people don't know how LDAP works, other than that "it's that thing that has my people don't know how LDAP works, other than that "it's that thing that has my
password". password."
By the end of this guide, we'll have a simple LDAP server that accomplishes a By the end of this guide, we'll have a simple LDAP server that accomplishes a
"real" task. "real" task.
# What exactly is LDAP? # What exactly is LDAP?
If you haven't already read the [wikipedia](http://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol) If you haven't already read the
entry, LDAP is the "Lightweight Directory Access Protocol". A directory service [wikipedia](http://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol)
basically breaks down as follows: entry (which you should go do right now), LDAP is the "Lightweight Directory
Access Protocol". A directory service basically breaks down as follows:
* A directory is a tree of entries (similar to but different than a FS). * A directory is a tree of entries (similar to but different than an FS).
* Every entry has a unique name in the tree. * Every entry has a unique name in the tree.
* An entry is a set of attributes. * An entry is a set of attributes.
* An attribute is a key/value(s) pairing (multival is natural). * An attribute is a key/value(s) pairing (multivalue is natural).
It might be helpful to visualize that: It might be helpful to visualize:
o=example o=example
/ \ / \
@ -40,7 +41,7 @@ It might be helpful to visualize that:
keyid=foo keyid=foo
And let's say we wanted to look at the record cn=john in that tree: Let's say we wanted to look at the record cn=john:
dn: cn=john, ou=users, o=example dn: cn=john, ou=users, o=example
cn: john cn: john
@ -49,7 +50,7 @@ And let's say we wanted to look at the record cn=john in that tree:
email: john.smith@example.com email: john.smith@example.com
objectClass: person objectClass: person
Then there's a few things to note: A few things to note:
* All names in a directory tree are actually referred to as a _distinguished * All names in a directory tree are actually referred to as a _distinguished
name_, or _dn_ for short. A dn is comprised of attributes that lead to that name_, or _dn_ for short. A dn is comprised of attributes that lead to that
@ -62,47 +63,46 @@ traditional ORM.
* An _objectclass_ defines what _attributes_ an entry can have (on the ORM * An _objectclass_ defines what _attributes_ an entry can have (on the ORM
analogy, an _attribute_ would be like a column). analogy, an _attribute_ would be like a column).
That's really it. LDAP really then is the protocol for interacting with the That's it. LDAP, then, is the protocol for interacting with the directory tree,
directory tree, and it's pretty comprehensively specified for common operations, and it's comprehensively specified for common operations, like
like add/update/delete and importantly, search. Really, the power of LDAP add/update/delete and importantly, search. Really, the power of LDAP comes
really comes through the search operations defined in the protocol, which are through the search operations defined in the protocol, which are richer
richer than HTTP query string filtering, but less powerful than full SQL. If it than HTTP query string filtering, but less powerful than full SQL. You can
helps, you can think of LDAP as a NoSQL/document store with a well-defined query think of LDAP as a NoSQL/document store with a well-defined query syntax.
syntax.
So, why isn't LDAP more popular for a lot of applications? Like anything else So, why isn't LDAP more popular for a lot of applications? Like anything else
that has "simple" or "lightweight" in the name, it's not really that that has "simple" or "lightweight" in the name, it's not really that
lightweight, and in particular, almost all of the implementations of LDAP stem lightweight. In particular, almost all of the implementations of LDAP stem
from the original University of Michigan codebase written in 1996. At that from the original University of Michigan codebase written in 1996. At that
time, the original intention of LDAP was to be an IP-accessible gateway to the time, the original intention of LDAP was to be an IP-accessible gateway to the
much more complex X.500 directories, which really means that a lot of that much more complex X.500 directories, which means that a lot of that
baggage has carried through to today. That makes for a high barrier to entry, baggage has carried through to today. That makes for a high barrier to entry,
when really most applications just don't need most of those features. when most applications just don't need most of those features.
## How is ldapjs any different? ## How is ldapjs any different?
Well, on the one hand, since ldapjs has to be 100% wire compatible with LDAP to Well, on the one hand, since ldapjs has to be 100% wire compatible with LDAP to
be useful, it's not, but on the other hand, there are no forced assumptions be useful, it's not. On the other hand, there are no forced assumptions about
about what you need and don't for your use of a directory system. For example, what you need and don't need for your use of a directory system. For example,
want to run with no-schema in OpenLDAP/389DS/et al? Good luck. Most of the want to run with no-schema in OpenLDAP/389DS/et al? Good luck. Most of the
server implementations support arbitrary "backends" for persistence, but really server implementations support arbitrary "backends" for persistence, but really
you'll be using [BDB](http://www.oracle.com/technetwork/database/berkeleydb/overview/index.html). you'll be using [BDB](http://www.oracle.com/technetwork/database/berkeleydb/overview/index.html).
Want to run schemaless in ldapjs, or wire it up with some mongoose models? No Want to run schema-less in ldapjs, or wire it up with some mongoose models? No
problem. Want to back it to redis? Should be able to get some basics up in a problem. Want to back it to redis? Should be able to get some basics up in a
day or two. day or two.
Basically, the ldapjs philospohy is to deal with the "muck" of LDAP, and then Basically, the ldapjs philospohy is to deal with the "muck" of LDAP, and then
get out of the way so you can just use the "good parts". get out of the way so you can just use the "good parts."
# Ok, cool. Learn me some LDAP! # Ok, cool. Learn me some LDAP!
Ok, so with the initial fluff out of the way, let's do something crazy to teach With the initial fluff out of the way, let's do something crazy to teach
you some LDAP. Let's put an LDAP server up over the top of your (Linux) host's you some LDAP. Let's put an LDAP server up over the top of your (Linux) host's
/etc/passwd and /etc/group files. Usually sysadmins "go the other way", and /etc/passwd and /etc/group files. Usually sysadmins "go the other way," and
replace /etc/passwd with a PAM module to LDAP, so while this is probably not replace /etc/passwd with a PAM module to LDAP. While this is probably not
a super useful real-world use case, it will teach you some of the basics. a super useful real-world use case, it will teach you some of the basics.
Oh, and if it is useful to you, then that's gravy. If it is useful to you, then that's gravy.
## Install ## Install
@ -112,9 +112,10 @@ respectively. After that, run:
$ npm install ldapjs $ npm install ldapjs
Also, 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
installed on your system, but if not, you can get it from brew/apt/yum/... installed on your system, but if not, you can get it from brew/apt/yum/your
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:
@ -147,9 +148,9 @@ LDAP clients will initiate a bind anyway (OpenLDAP will), so let's add it in
and get it out of our way. and get it out of our way.
What we're going to do is add a "root" user to our LDAP server. This root user What we're going to do is add a "root" user to our LDAP server. This root user
has no correspondance to our Unix root user, it's just something we're making up 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. Great, so go and going to use for allowing an (LDAP) admin to do anything. To do so, add
ahead and add this code into your file: this code into your file:
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')
@ -168,8 +169,8 @@ On to the meat of the method. What's up with this?
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret') if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
So, 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
of. It allows cn=root *and any children* into that handler. So the entries of. It allows cn=root *and any children* into that handler. So the entries
`cn=root` and `cn=evil, cn=root` would both match and flow into this handler. `cn=root` and `cn=evil, cn=root` would both match and flow into this handler.
Hence that check. The second check `req.credentials` is probably obvious, but Hence that check. The second check `req.credentials` is probably obvious, but
@ -204,11 +205,10 @@ And again with the correct one:
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. Note that this time, we got another their CLI less annonyingly noisy. This time, we got another `No such object`
`No such object` error, but this time note that it's for the tree error, but it's for the tree `o=myhost`. That means our bind went through, and
`o=myhost`. That means our bind went through, and our search failed, our search failed, since we haven't yet added a search handler. Just one more
since we haven't yet added a search handler. Just one more small thing to do small thing to do first.
first.
Remember earlier I said there was no authorization rules baked into LDAP? Well, Remember earlier I said there was no authorization rules baked into LDAP? Well,
we added a bind route, so the only user that can authenticate is `cn=root`, but we added a bind route, so the only user that can authenticate is `cn=root`, but
@ -229,27 +229,26 @@ oriented, so we check that the connection remote user was indeed our `cn=root`
## Search ## Search
Ok, 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 jsmith:x:1001:1000:Joe Smith,Room 1007,(234)555-8910,(234)555-0044,email:/home/jsmith:/bin/sh
That maps to: The sample record above maps to:
||jsmith||user name|| ||jsmith||user name.||
||x||historically this contained the password hash, but that's usually in /etc/shadow now, so you get an 'x'|| ||x||historically this contained the password hash, but that's usually in /etc/shadow now, so you get an 'x'.||
||1001||the unix numeric user id|| ||1001||the unix numeric user id.||
||1000||the unix numeric group id. (primary)|| ||1000||the unix numeric group id. (primary).||
||'Joe Smith,...'||the "gecos", which is a description, and is usually a comma separated list of contact details|| ||'Joe Smith,...'||the "gecos," which is a description, and is usually a comma separated list of contact details.||
||/home/jsmith||the user's home directory|| ||/home/jsmith||the user's home directory.||
||/bin/sh||the user's shell|| ||/bin/sh||the user's shell.||
Great, let's some handlers to parse that and transform it into an LDAP search Let's 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 `var fs = require('fs');` at the top of the
source file): source file).
First, let's make a handler that just loads the "user database" for us in a First, make a handler that just loads the "user database" in a "pre" handler:
"pre" handler:
function loadPasswdFile(req, res, next) { function loadPasswdFile(req, res, next) {
fs.readFile('/etc/passwd', 'utf8', function(err, data) { fs.readFile('/etc/passwd', 'utf8', function(err, data) {
@ -336,25 +335,25 @@ the tree for entries that might match the search filter, which above is
`cn=root`. `cn=root`.
In this little LDAP example, we're mostly throwing out any qualification of the In this little LDAP example, we're mostly throwing out any qualification of the
"tree", since there's not actually a tree in /etc/passwd (we will extend later "tree," since there's not actually a tree in /etc/passwd (we will extend later
with /etc/group). Remember how I said ldapjs gets out of the way and doesn't with /etc/group). Remember how I said ldapjs gets out of the way and doesn't
force anything on you. Here's an example. If we wanted an LDAP server to run force anything on you? Here's an example. If we wanted an LDAP server to run
over the filesystem, we actually would use this, but here, meh. over the filesystem, we actually would use this, but here, meh.
Next, "cn=root" is the search 'filter'. LDAP has a rich specification of Next, `cn=root` is the search "filter". LDAP has a rich specification of
filters, where you can specify `and`, `or`, `not`, `>=`, `<=`, `equal`, filters, where you can specify `and`, `or`, `not`, `>=`, `<=`, `equal`,
`wildcard`, `present` and a few other esoteric things. Really, `equal`, `wildcard`, `present` and a few other esoteric things. Really, `equal`,
`wildcard`, `present` and the boolean operators are all you'll likely ever need. `wildcard`, `present` and the boolean operators are all you'll likely ever need.
So, the filter `cn=root` is an 'equality' filter, and says to only return So, the filter `cn=root` is an "equality" filter, and says to only return
entries that have attributes that match that. In the second invocation, we used entries that have attributes that match that. In the second invocation, we used
a 'presence' filter, to say 'return any entries that have an objectclass' a 'presence' filter, to say 'return any entries that have an objectclass'
attribute, which in LDAP parlance is saying "give me everything". attribute, which in LDAP parlance is saying "give me everything."
### The code ### The code
So in the code above, let's ignore the fs and split stuff, since really all we In the code above, let's ignore the fs and split stuff, since really all we
did was read in /etc/passwd line by line. After that, we looked at each record did was read in /etc/passwd line by line. After that, we looked at each record
and made the cheesiest transform ever, which is making up a "search entry". A 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:
@ -373,9 +372,9 @@ of attributes. So that's why we did this:
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
record with `res.send`. Note in this little example we're running O(n), so for record with `res.send`. In this little example we're running O(n), so for
something big and/or slow, you'd have to do some work to effectively write a something big and/or slow, you'd have to do some work to effectively write a
query planner (or just not support it...); for some reference code, check out query planner (or just not support it...). For some reference code, check out
`node-ldapjs-riak`, which takes on the fairly difficult task of writing a 'full' `node-ldapjs-riak`, which takes on the fairly difficult task of writing a 'full'
LDAP server over riak. LDAP server over riak.
@ -468,7 +467,7 @@ Then, you'll need to be root to have this running, so start your server with
shell: /bin/bash shell: /bin/bash
description: Created via ldapadd description: Created via ldapadd
Now go ahead and invoke like: Now go ahead and invoke with:
$ ldapadd -H ldap://localhost:1389 -x -D cn=root -w secret -f ./user.ldif $ ldapadd -H ldap://localhost:1389 -x -D cn=root -w secret -f ./user.ldif
adding new entry "cn=ldapjs, ou=users, o=myhost" adding new entry "cn=ldapjs, ou=users, o=myhost"
@ -499,7 +498,7 @@ As before, here's a breakdown of the code:
if (entry.objectclass.indexOf('unixUser') === -1) if (entry.objectclass.indexOf('unixUser') === -1)
return next(new ldap.ConstraintViolation('entry must be a unixUser')); return next(new ldap.ConstraintViolation('entry must be a unixUser'));
Here's a few new things: A few new things:
* We mounted this handler at `ou=users, o=myhost`. Why? What if we want to * We mounted this handler at `ou=users, o=myhost`. Why? What if we want to
extend this little project with groups? We probably want those under a extend this little project with groups? We probably want those under a
@ -519,16 +518,16 @@ looking at.
one point: attribute names are case-insensitive, so ldapjs converts them all to one point: attribute names are case-insensitive, so ldapjs converts them all to
lower case (note the client sent _objectClass_ over the wire). lower case (note the client sent _objectClass_ over the wire).
After that, we really just delegated off to the _useradd_ command. AFAIK there After that, we really just delegated off to the _useradd_ command. As far as I
is not a node.js module that wraps up `getpwent` and friends, otherwise we'd use know, there is not a node.js module that wraps up `getpwent` and friends,
that. otherwise we'd use that.
Now, what's missing? Oh, right, we need to let you set a password. Well, let's Now, what's missing? Oh, right, we need to let you set a password. Well, let's
support that via the _modify_ command. support that via the _modify_ command.
## Modify ## Modify
So unlike HTTP "partial" document updates are fully specified as part of the 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:
@ -571,10 +570,10 @@ Go ahead and add the following code into your source file:
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
is the 'standard' LDAP attribute for passwords, for whatever that's worth; if is the 'standard' LDAP attribute for passwords; if you think it's easier to use
you think it's easier to use 'password', knock yourself out), and then just 'password', knock yourself out), and then just delegating to the `chpasswd`
delegating to the `chpasswd` command (which lets you change a user's password command (which lets you change a user's password over stdin). Next, go ahead
over stdin). Next, go ahead and create a `passwd.ldif` file: and create a `passwd.ldif` file:
dn: cn=ldapjs, ou=users, o=myhost dn: cn=ldapjs, ou=users, o=myhost
changetype: modify changetype: modify
@ -582,17 +581,17 @@ over stdin). Next, go ahead and create a `passwd.ldif` file:
userPassword: secret userPassword: secret
- -
And then run the OpenLDAP CLI like: And then run the OpenLDAP CLI:
$ ldapmodify -H ldap://localhost:1389 -x -D cn=root -w secret -f ./passwd.ldif $ 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. Ok, 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 delte the user. the last "mainline" piece of work out of the way, and delete the user.
## Delete ## Delete
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 :). Go ahead and 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) { server.del('ou=users, o=myhost', pre, function(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])
@ -625,5 +624,16 @@ And then run the following command:
$ ldapdelete -H ldap://localhost:1389 -x -D cn=root -w secret "cn=ldapjs, ou=users, o=myhost" $ ldapdelete -H ldap://localhost:1389 -x -D cn=root -w secret "cn=ldapjs, ou=users, o=myhost"
This should be pretty much self-explanatory by now :)
# Where to go from here
The complete source code for this example server is available in
[examples](/examples.html). Make sure to read up on the [server](/server.html)
and [client](/client.html) APIs. If you're looking for a "drop in" solution,
take a look at [ldapjs-riak](https://github.com/mcavage/node-ldapjs-riak).
[Mozilla](https://wiki.mozilla.org/Mozilla_LDAP_SDK_Programmer%27s_Guide/Understanding_LDAP)
still maintains some web pages with LDAP overviews if you look around, if you're
looking for more tutorials. After that, you'll need to work your way through
the [RFCs](http://tools.ietf.org/html/rfc4510) as you work through the APIs in
ldapjs.

View File

@ -6,13 +6,15 @@ logo-font-family: google:Aldrich, Verdana, sans-serif
header-font-family: google:Aldrich, Verdana, sans-serif header-font-family: google:Aldrich, Verdana, sans-serif
--- ---
LDAP for [node.js](http://nodejs.org). <div id="indextagline">
Reimagining <a href="http://tools.ietf.org/html/rfc4510" id="indextaglink">LDAP</a> for <a id="indextaglink" href="http://nodejs.org">Node.js</a>
</div>
# Overview # Overview
ldapjs is a pure JavaScript, from-scratch framework for implementing ldapjs is a pure JavaScript, from-scratch framework for implementing
[LDAP](http://tools.ietf.org/html/rfc4510) clients and servers in [LDAP](http://tools.ietf.org/html/rfc4510) clients and servers in
[node.js](http://nodejs.org). It is intended for developers used to interacting [Node.js](http://nodejs.org). It is intended for developers used to interacting
with HTTP services in node and [express](http://expressjs.com). with HTTP services in node and [express](http://expressjs.com).
var server = require('ldapjs').createServer(); var server = require('ldapjs').createServer();

View File

@ -18,13 +18,13 @@ The code to create a new server looks like:
var server = ldap.createServer(); var server = ldap.createServer();
Full list of options: The full list of options is:
||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|| ||log4js||You can optionally pass in a log4js instance the client will use to acquire a logger. 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|| ||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|| ||key||A PEM-encoded private key that corresponds to _certificate_ for SSL.||
## Properties on server ## Properties on the server object
### maxConnections ### maxConnections
@ -134,31 +134,30 @@ server up with riak looks like:
backend.add()); backend.add());
... ...
Basically, 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
backend object, if applicable. After that you can pass in an arbitrary backend object. After that you can pass in an arbitrary combination of
combination of functions in the form `f(req, res, next)` or Arrays of functions functions in the form `f(req, res, next)` or arrays of functions of the same
of the same signature (ldapjs will unroll them). signature (ldapjs will unroll them).
Unlike HTTP, LDAP operations do not have a heterogenous format, so each Unlike HTTP, LDAP operations do not have a heterogeneous wire format, so each
operation in the rest of the document includes documentation for the operation requires specific methods/fields on the request/response objects.
request/response objects appropriate to that operation type.
## Common Request Elements ## Common Request Elements
All request objects has the `dn` getter on it, which is "context-sensitive" All request objects have the `dn` getter on it, which is "context-sensitive"
and returns the point in the tree that the operation wants to operate on. The 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 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 name for just about every op. So, ldapjs calls it `dn`. The DN object itself
is documented at [DN](/dn.html). is documented at [DN](/dn.html).
All requests have an optional array of `Control` objects. `Control` will have All requests have an optional array of `Control` objects. `Control` will have
the properties `type` (string), `criticality` (boolean), and optionally a string the properties `type` (string), `criticality` (boolean), and optionally, a
`value`. string `value`.
All request objects will have a `connection` object, which is the `net.Socket` 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. 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 The most important property to pay attention to is the `bindDN` property
which will be an instance of an `ldap.DN` object. This is what the client 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 authenticated as on this connection. If the client didn't bind, then a DN object
will be there defaulted to `cn=anonymous`. will be there defaulted to `cn=anonymous`.
@ -233,7 +232,7 @@ No extra methods above an `LDAPResult` API call.
# Add # Add
Adds a mount in the tree to perform LDAP adds with. Example: Adds a mount in the tree to perform LDAP adds with.
server.add('ou=people, o=example', function(req, res, next) { server.add('ou=people, o=example', function(req, res, next) {
console.log('DN: ' + req.dn.toString()); console.log('DN: ' + req.dn.toString());
@ -247,12 +246,12 @@ AddRequest objects have the following properties:
### entry ### entry
The DN the client is attempting to add (note this is the same as the `dn` The DN the client is attempting to add (this is the same as the `dn`
property). property).
### attributes ### attributes
The set of attributes in this entry. Note that this will be an array of The set of attributes in this entry. This will be an array of
`Attribute` objects (which have a type and an array of values). This directly `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 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 `toObject()` and iterate that way, since that will transform an AddRequest into
@ -278,7 +277,7 @@ No extra methods above an `LDAPResult` API call.
# Search # Search
Adds a handler for the LDAP search operation. Example: Adds a handler for the LDAP search operation.
server.search('o=example', function(req, res, next) { server.search('o=example', function(req, res, next) {
console.log('base object: ' + req.dn.toString()); console.log('base object: ' + req.dn.toString());
@ -305,7 +304,7 @@ The DN the client is attempting to start the search at (equivalent to `dn`).
### derefAliases ### derefAliases
Will be an integer (defined in the LDAP protocol). Defaults to '0' (meaning An integer (defined in the LDAP protocol). Defaults to '0' (meaning
never deref). never deref).
### sizeLimit ### sizeLimit
@ -321,13 +320,13 @@ Defaults to '0' (unlimited).
### typesOnly ### typesOnly
Whether to return only the names of attributes, and not the values. Defaults to 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. 'false'. ldapjs will take care of this for you.
### filter ### filter
The [filter](/filters.html) object that the client requested. Notably this has 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 `matches()` method on it that you can leverage. For an example of
a filter, take a look at the ldapjs-riak source. introspecting a filter, take a look at the ldapjs-riak source.
### attributes ### attributes
@ -338,10 +337,10 @@ will automatically handle this for you.
### send(entry) ### send(entry)
Allows you to send a `SearchEntry` object. Note that you do not need to Allows you to send a `SearchEntry` object. You do not need to
explicitly pass in a `SearchEntry` object, and can instead just send a plain 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()`.
Example:
server.search('o=example', function(req, res, next) { server.search('o=example', function(req, res, next) {
var obj = { var obj = {
@ -360,7 +359,7 @@ Example:
# modify # modify
Allows you to handle an LDAP modify operation. Example: Allows you to handle an LDAP modify operation.
server.modify('o=example', function(req, res, next) { server.modify('o=example', function(req, res, next) {
console.log('DN: ' + req.dn.toString()); console.log('DN: ' + req.dn.toString());
@ -378,7 +377,7 @@ ModifyRequest objects have the following properties:
### object ### object
The DN the client is attempting to update (note this is the same as the `dn` The DN the client is attempting to update (this is the same as the `dn`
property). property).
### changes ### changes
@ -388,11 +387,11 @@ details on the `Change` object.
## Change ## Change
The Change object will have the following properties: The `Change` object will have the following properties:
### operation ### operation
A string, and will be one of: 'add', 'delete', 'replace'. A string, and will be one of: 'add', 'delete', or 'replace'.
### modification ### modification
@ -405,7 +404,7 @@ No extra methods above an `LDAPResult` API call.
# del # del
Allows you to handle an LDAP delete operation. Example: Allows you to handle an LDAP delete operation.
server.delete('o=example', function(req, res, next) { server.delete('o=example', function(req, res, next) {
console.log('DN: ' + req.dn.toString()); console.log('DN: ' + req.dn.toString());
@ -416,7 +415,7 @@ Allows you to handle an LDAP delete operation. Example:
### entry ### entry
The DN the client is attempting to delete (note this is the same as the `dn` The DN the client is attempting to delete (this is the same as the `dn`
property). property).
## DeleteResponse ## DeleteResponse
@ -425,7 +424,7 @@ No extra methods above an `LDAPResult` API call.
# compare # compare
Allows you to handle an LDAP compare operation. Example: Allows you to handle an LDAP compare operation.
server.compare('o=example', function(req, res, next) { server.compare('o=example', function(req, res, next) {
console.log('DN: ' + req.dn.toString()); console.log('DN: ' + req.dn.toString());
@ -438,12 +437,12 @@ Allows you to handle an LDAP compare operation. Example:
### entry ### entry
The DN the client is attempting to compare (note this is the same as the `dn` The DN the client is attempting to compare (this is the same as the `dn`
property). property).
### attribute ### attribute
Will be the string name of the attribute to compare values of. The string name of the attribute to compare values of.
### value ### value
@ -452,12 +451,12 @@ The string value of the attribute to compare.
## CompareResponse ## CompareResponse
The `end()` method for compare takes a boolean, as opposed to a numeric code 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 (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. that, there are no extra methods above an `LDAPResult` API call.
# modifyDN # modifyDN
Allows you to handle an LDAP modifyDN operation. Example: Allows you to handle an LDAP modifyDN operation.
server.modifyDN('o=example', function(req, res, next) { server.modifyDN('o=example', function(req, res, next) {
console.log('DN: ' + req.dn.toString()); console.log('DN: ' + req.dn.toString());
@ -473,7 +472,7 @@ Allows you to handle an LDAP modifyDN operation. Example:
### entry ### entry
The DN the client is attempting to rename (note this is the same as the `dn` The DN the client is attempting to rename (this is the same as the `dn`
property). property).
### newRdn ### newRdn
@ -482,13 +481,12 @@ The leaf RDN the client wants to rename this entry to. This will be a DN object.
### deleteOldRdn ### deleteOldRdn
boolean. Whether or not to delete the old RDN (i.e., rename vs copy). Defaults Whether or not to delete the old RDN (i.e., rename vs copy). Defaults to 'true'.
to true.
### newSuperior ### newSuperior
Optional (DN). If the modifyDN operation wishes to relocate the entry in the Optional (DN). If the modifyDN operation wishes to relocate the entry in the
tree, the newSuperior field will contain the new parent. tree, the `newSuperior` field will contain the new parent.
## ModifyDNResponse ## ModifyDNResponse
@ -498,10 +496,9 @@ No extra methods above an `LDAPResult` API call.
Allows you to handle an LDAP extended operation. Extended operations are pretty Allows you to handle an LDAP extended operation. Extended operations are pretty
much arbitrary extensions, by definition. Typically the extended 'name' is an 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 OID, but ldapjs makes no such restrictions; it just needs to be a string.
that unlike the other operations, extended operations don't map to any location Unlike the other operations, extended operations don't map to any location in
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.
Example:
// LDAP whoami // LDAP whoami
server.exop('1.3.6.1.4.1.4203.1.11.3', function(req, res, next) { server.exop('1.3.6.1.4.1.4203.1.11.3', function(req, res, next) {
@ -516,8 +513,8 @@ Example:
### name ### name
String. Will always be a match to the route-defined name. But the client Will always be a match to the route-defined name. Clients must include this
includes this in their requests. in their requests.
### value ### value
@ -538,7 +535,8 @@ The arbitrary (string) value to send back as part of the response.
ldapjs by default provides an unbind handler that just disconnects the client 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 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. if you need to clean up any items in your backend, or perform any other cleanup
tasks you need to.
server.unbind(function(req, res, next) { server.unbind(function(req, res, next) {
res.end(); res.end();
@ -547,5 +545,5 @@ if you need to clean up any items in your backend, for example.
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
ultimately calls `net.Socket.end()` for you. There are no properties available 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 on either the request or response objects, except, of course, for `end()` on the
response. response.