First commit

This commit is contained in:
2025-10-08 11:12:59 -04:00
commit b0605a28a9
820 changed files with 100317 additions and 0 deletions

1
node_modules/ldapjs/docs/branding/public/CNAME generated vendored Normal file
View File

@ -0,0 +1 @@
ldapjs.org

View File

@ -0,0 +1,266 @@
/* ---- general styles */
body {
font: 13px "Lucida Grande", "Lucida Sans Unicode", arial, sans-serif;
line-height: 1.53846; /* 20px */
color: #4a3f2d;
}
:focus:not(:focus-visible) {
outline: 0;
}
h1,h2,h3 {
font-weight:normal;
}
h3{
margin-bottom:0;
}
ul, li {
margin:0px;
padding:0px;
}
ul {
margin-left:40px;
}
ul > li {
list-style:disc;
list-style-position:inside;
margin:10px 0px;
}
hr {
border:none;
width:98%;
margin-left:-10px;
border-top:1px solid #CCCCCC;
border-bottom:1px solid #FFFFFF;
}
code,
pre {
border:1px solid #CCCCCC;
background:#F2F0EE;
-webkit-border-radius:2px;
-moz-border-radius:2px;
border-radius:2px;
white-space:pre-wrap;
}
code {
padding: 0 0.2em;
}
pre {
margin: 1em 0;
padding: .75em;
overflow: auto;
padding:10px 1.2em;
margin-top:0;
margin-bottom:20px;
}
pre code {
border: medium none;
padding: 0;
}
a code {
text-decoration: underline;
}
a {
color:#FD6512;
text-decoration:none;
}
h4 {
font-size: 85%;
margin: 0;
padding: 0;
line-height: 1em;
display: inline;
}
/* ---- header and sidebar */
#header {
background:#C3BDB3;
background:#1C313C;
height:66px;
left:0px;
position:absolute;
top:0px;
width:100%;
z-index:1;
font-size:0.7em;
}
#header h1 {
width: 424px;
height: 35px;
display:block;
background: url(../img/logo.svg) no-repeat;
line-height:2.1em;
padding:0;
padding-left:140px;
margin-top:18px;
margin-left:20px;
color:white;
text-transform: uppercase;
}
#sidebar {
background-color:#EDEBEA;
bottom:0px;
left:0px;
overflow:auto;
padding:20px 0px 0px 15px;
position:absolute;
top:66px;
width:265px;
z-index:1;
}
#content {
top:64px;
bottom:0px;
right:0px;
left:290px;
padding:20px 30px 400px;
position:absolute;
overflow:auto;
z-index:0;
}
#sidebar h1 {
font-size:1.2em;
padding:0px;
margin-top:15px;
margin-bottom:3px;
}
#sidebar ul {
margin:3px 0 10px 0;
}
#sidebar ul ul {
margin:3px 0 5px 10px;
}
#sidebar li {
margin:0;
padding:0;
font-size:0.9em;
}
#sidebar li,
#sidebar li a {
color:#5C5954;
list-style:none;
padding:1px 0px 1px 2px;
}
/* ---- intro */
.intro {
color:#29231A;
padding: 22px 25px;
background: #EDEBEA;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
-o-border-radius: 5px;
border-radius: 5px;
margin-bottom:40px;
}
.intro h1 {
color: #1C313C;
}
.intro h3 {
margin: 5px 0px 3px;
font-size: 100%;
font-weight: bold;
}
.intro ul {
list-style-type:disc;
padding-left:20px;
margin-left:0;
}
.intro ul li{
margin:0;
}
.intro p {
padding-left:20px;
margin: 5px 0px 3px;
}
h2 {
overflow: auto;
margin-top: 60px;
border-top: 2px solid #979592;
z-index: 3;
}
h1 + h2 {
margin-top: 0px;
}
h2 span {
background: #979592;
float:right;
color:#fff;
margin:0;
margin-left:3px;
padding:0.3em 0.7em;
font-size: 0.55em;
word-spacing: 0.8em; /* separate verb from path */
color:#fff;
}
/*---- print media */
@media print {
body { background:white; color:black; margin:0; }
#sidebar {
display: none;
}
#content {
position: relative;
padding: 5px;
left: 0px;
top: 0px;
}
h1, h2, h4 {
page-break-after: avoid;
}
pre {
page-break-inside: avoid;
}
}
/* tables still need cellspacing="0" in the markup */
table {
border-collapse:collapse; border-spacing:0;
margin: 20px 0;
}
th,
td {
border: solid #aaa;
border-width: 1px 0;
line-height: 23px;
padding: 0 12px;
text-align: left;
vertical-align: text-bottom;
}
th {
border-collapse: separate;
}
tbody tr:nth-child(odd) {
background-color: #f2f0ee;
}

View File

@ -0,0 +1 @@
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="125" height="34.1" viewBox="0 0 146.25 39.96"><defs><path d="M-2.21-3.96h150v45.93h-150z"/><clipPath><use xlink:href="#a" overflow="visible"/></clipPath><clipPath><use xlink:href="#c-4" overflow="visible"/></clipPath></defs><clipPath><use xlink:href="#a" overflow="visible"/></clipPath><g clip-path="url(#b-8)" fill="#f60"><defs><path d="m-2.21-3.96 150 0 0 45.93-150 0z"/></defs><clipPath><use xlink:href="#c-4" overflow="visible" x="0" y="0" width="100" height="100"/></clipPath><path d="m15.74 31.29c8.61 0 15.6-6.98 15.6-15.59C31.34 7.08 24.35 0.1 15.74 0.1 7.13 0.1 0.14 7.08 0.14 15.7c0 8.61 6.98 15.6 15.6 15.6" style="fill-rule:evenodd;fill:#f60"/><path d="m12.96 7.35c0-0.32 0.26-0.59 0.59-0.59l4.38 0c0.33 0 0.59 0.26 0.59 0.59l0 5.57 5.57 0c0.33 0 0.59 0.26 0.59 0.59l0 4.38c0 0.33-0.26 0.59-0.59 0.59l-5.57 0 0 5.57c0 0.33-0.26 0.59-0.59 0.59l-4.37 0c-0.32 0-0.59-0.26-0.59-0.59l0-5.57-5.57 0c-0.32 0-0.59-0.26-0.59-0.59l0-4.37c0-0.32 0.26-0.59 0.59-0.59l5.57 0z" style="fill-rule:evenodd;fill:#fff"/></g><g clip-path="url(#b)" fill="#fff"><defs><path d="m-2.21-3.96 150 0 0 45.93-150 0z"/></defs><clipPath><use height="100" width="100" y="0" x="0" overflow="visible" xlink:href="#c"/></clipPath><path d="m35.84 25.26c1.22 0.94 2.63 1.81 4.52 1.81 2.87 0 3.62-1.73 3.62-4.01l0-19.96 3.11 0 0 16.27c0 1.42 0 2.95-0.08 4.36-0.16 3.77-2.08 5.97-6.52 5.97-3.06 0-5.31-1.26-6.21-2.24zm29.51-5.7c0-4.72-1.37-8.02-5.19-8.02-3.73 0-5.42 3.14-5.42 7.39 0 3.89 0.79 8.49 5.27 8.49 3.73 0 5.35-3.57 5.35-7.86M60.41 9.14c2.71 0 8.06 0.87 8.06 9.75 0 7.66-3.81 10.89-8.72 10.89-4.99 0-8.06-3.38-8.06-10.45 0-8.13 4.83-10.18 8.72-10.18m26.88 19.3 0-18.74-2.99 0 0 11.71c0 2.71-1.81 4.6-4.79 4.6-4.17 0-4.44-2.28-4.44-5.27l0-11.04-3.06 0 0 11.87c0 4.48 2.16 6.09 5.5 6.45-1.93 1.26-4.32 3.54-4.32 6.72 0 3.03 1.93 4.95 5.93 4.95 4.17 0 6.68-2 7.66-5.82 0.35-1.38 0.51-3.69 0.51-5.42m-7.86 8.88c3.58 0 4.83-3.1 4.83-7.39l0-3.42c-4.72 1.97-8.13 4.09-8.13 7.82 0 1.93 1.14 2.99 3.3 2.99M99.94 9.14c-5.97 0-9.12 4.79-9.12 10.61 0 5.86 2.59 10.02 8.72 10.02 3.14 0 5.42-1.41 6.41-2.24l-1.18-2c-0.75 0.55-2.4 1.81-5.03 1.81-4.16 0-5.74-3.38-5.89-6.6l1.02 0.04c3.81 0 10.81-0.94 10.81-6.76 0-2.91-2.08-4.87-5.74-4.87m-0.16 2.28c-4.2 0-5.82 3.97-5.93 7.07l0.79 0.04c2.67 0 8.17-0.63 8.17-4.17 0-1.85-1.26-2.95-3.03-2.95m13.01 17.8 0-12.81c0-2.36 2.28-4.83 5.31-4.83 3.3 0 3.93 2.36 3.93 5.27l0 12.38 3.07 0 0-13.32c0-4.48-2.2-6.76-6.25-6.76-2.56 0-4.83 1.18-6.33 3.38l-0.35-2.83-2.63 0 0.2 2.95 0 16.58 3.07 0zm16.82-27.31 0 21.97c0 2.87 0.16 5.9 5.38 5.9 1.57 0 3.3-0.51 4.44-1.3l-0.9-2.04c-0.67 0.39-1.65 0.94-2.99 0.94-1.85 0-2.87-0.82-2.87-3.42l0-11.63 5.58 0 0-2.63-5.58 0 0-7.78zm12.22 1.8c0 1.14 0.91 1.99 2 1.99 1.08 0 1.99-0.85 1.99-1.99 0-1.12-0.91-1.97-1.99-1.97-1.09 0-2 0.85-2 1.97m0.36 0c0-0.95 0.71-1.68 1.64-1.68 0.92 0 1.63 0.73 1.63 1.68 0 0.97-0.71 1.7-1.63 1.7-0.93 0-1.64-0.73-1.64-1.7m0.86 1.17 0.36 0 0-1 0.38 0 0.63 1 0.39 0-0.66-1.02c0.35-0.04 0.61-0.21 0.61-0.63 0-0.44-0.26-0.66-0.81-0.66l-0.9 0 0 2.32zm0.36-2.02 0.48 0c0.24 0 0.51 0.05 0.51 0.36 0 0.37-0.29 0.38-0.61 0.38l-0.38 0z" clip-path="url(#d)" style="fill-rule:evenodd;fill:#fff"/></g></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

39
node_modules/ldapjs/docs/branding/template.html generated vendored Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>%(title)s</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="media/css/style.css">
<link rel="stylesheet" type="text/css" href="media/css/highlight.css">
</head>
<body>
<div id="header">
<h1>%(title)s Documentation</h1>
</div>
<div id="sidebar">
<div>Sections</div>
<span>
<ul>
<li><div><a href="index.html">Home</a></div></li>
<li><div><a href="guide.html">Guide</a></div></li>
<li><div><a href="examples.html">Examples</a></div></li>
<li><div><a href="client.html">Client API</a></div></li>
<li><div><a href="server.html">Server API</a></div></li>
<li><div><a href="dn.html">DN API</a></div></li>
<li><div><a href="filters.html">Filters API</a></div></li>
<li><div><a href="errors.html">Error API</a></div></li>
</ul>
</span>
<div>Contents</div>
</span>
%(toc_html)s
</div>
<div id="content">
%(content)s
</div><!-- end #content -->
</body>
</html>

491
node_modules/ldapjs/docs/client.md generated vendored Normal file
View File

@ -0,0 +1,491 @@
---
title: Client API | ldapjs
---
# ldapjs Client API
<div class="intro">
This document covers the ldapjs client API and assumes that you are familiar
with LDAP. If you're not, read the [guide](guide.html) first.
</div>
# Create a client
The code to create a new client looks like:
```js
const ldap = require('ldapjs');
const client = ldap.createClient({
url: ['ldap://127.0.0.1:1389', 'ldap://127.0.0.2:1389']
});
client.on('connectError', (err) => {
// handle connection error
})
```
You can use `ldap://` or `ldaps://`; the latter would connect over SSL (note
that this will not use the LDAP TLS extended operation, but literally an SSL
connection to port 636, as in LDAP v2). The full set of options to create a
client is:
|Attribute |Description |
|---------------|-----------------------------------------------------------|
|url |A string or array of valid LDAP URL(s) (proto/host/port) |
|socketPath |Socket path if using AF\_UNIX sockets |
|log |A compatible logger instance (Default: no-op logger) |
|timeout |Milliseconds client should let operations live for before timing out (Default: Infinity)|
|connectTimeout |Milliseconds client should wait before timing out on TCP connections (Default: OS default)|
|tlsOptions |Additional options passed to TLS connection layer when connecting via `ldaps://` (See: The TLS docs for node.js)|
|idleTimeout |Milliseconds after last activity before client emits idle event|
|reconnect |Try to reconnect when the connection gets lost (Default is false)|
### url
This parameter takes a single connection string or an array of connection strings
as an input. In case an array is provided, the client tries to connect to the
servers in given order. To achieve random server strategy (e.g. to distribute
the load among the servers), please shuffle the array before passing it as an
argument.
### Note On Logger
A passed in logger is expected to conform to the [Bunyan](https://www.npmjs.com/package/bunyan)
API. Specifically, the logger is expected to have a `child()` method. If a logger
is supplied that does not have such a method, then a shim version is added
that merely returns the passed in logger.
Known compatible loggers are:
+ [Bunyan](https://www.npmjs.com/package/bunyan)
+ [Pino](https://www.npmjs.com/package/pino)
### Note On Error Handling
The client is an `EventEmitter`. If you don't register an error handler and
e.g. a connection error occurs, Node.js will print a stack trace and exit the
process ([reference](https://nodejs.org/api/events.html#error-events)).
## Connection management
As LDAP is a stateful protocol (as opposed to HTTP), having connections torn
down from underneath you can be difficult to deal with. Several mechanisms
have been provided to mitigate this trouble.
### Reconnect
You can provide a Boolean option indicating if a reconnect should be tried. For
more sophisticated control, you can provide an Object with the properties
`initialDelay` (default: `100`), `maxDelay` (default: `10000`) and
`failAfter` (default: `Infinity`).
After the reconnect you maybe need to [bind](#bind) again.
## Client events
The client is an `EventEmitter` and can emit the following events:
|Event |Description |
|---------------|----------------------------------------------------------|
|error |General error |
|connectRefused |Server refused connection. Most likely bad authentication |
|connectTimeout |Server timeout |
|connectError |Socket connection error |
|setupError |Setup error after successful connection |
|socketTimeout |Socket timeout |
|resultError |Search result error |
|timeout |Search result timeout |
|destroy |After client is disconnected |
|end |Socket end event |
|close |Socket closed |
|connect |Client connected |
|idle |Idle timeout reached |
## Common patterns
The last two parameters in every API are `controls` and `callback`. `controls`
can be either a single instance of a `Control` or an array of `Control` objects.
You can, and probably will, omit this option.
Almost every operation has the callback form of `function(err, res)` where err
will be an instance of an `LDAPError` (you can use `instanceof` to switch).
You probably won't need to check the `res` parameter, but it's there if you do.
# bind
`bind(dn, password, controls, callback)`
Performs a bind operation against the LDAP server.
The bind API only allows LDAP 'simple' binds (equivalent to HTTP Basic
Authentication) for now. Note that all client APIs can optionally take an array
of `Control` objects. You probably don't need them though...
Example:
```js
client.bind('cn=root', 'secret', (err) => {
assert.ifError(err);
});
```
# add
`add(dn, entry, controls, callback)`
Performs an add operation against the LDAP server.
Allows you to add an entry (which is just a plain JS object), and as always,
controls are optional.
Example:
```js
const entry = {
cn: 'foo',
sn: 'bar',
email: ['foo@bar.com', 'foo1@bar.com'],
objectclass: 'fooPerson'
};
client.add('cn=foo, o=example', entry, (err) => {
assert.ifError(err);
});
```
# compare
`compare(dn, attribute, value, controls, callback)`
Performs an LDAP compare operation with the given attribute and value against
the entry referenced by dn.
Example:
```js
client.compare('cn=foo, o=example', 'sn', 'bar', (err, matched) => {
assert.ifError(err);
console.log('matched: ' + matched);
});
```
# del
`del(dn, controls, callback)`
Deletes an entry from the LDAP server.
Example:
```js
client.del('cn=foo, o=example', (err) => {
assert.ifError(err);
});
```
# exop
`exop(name, value, controls, callback)`
Performs an LDAP extended operation against an LDAP server. `name` is typically
going to be an OID (well, the RFC says it must be; however, ldapjs has no such
restriction). `value` is completely arbitrary, and is whatever the exop says it
should be.
Example (performs an LDAP 'whois' extended op):
```js
client.exop('1.3.6.1.4.1.4203.1.11.3', (err, value, res) => {
assert.ifError(err);
console.log('whois: ' + value);
});
```
# modify
`modify(name, changes, controls, callback)`
Performs an LDAP modify operation against the LDAP server. This API requires
you to pass in a `Change` object, which is described below. Note that you can
pass in a single `Change` or an array of `Change` objects.
Example:
```js
const change = new ldap.Change({
operation: 'add',
modification: {
type: 'pets',
values: ['cat', 'dog']
}
});
client.modify('cn=foo, o=example', change, (err) => {
assert.ifError(err);
});
```
## Change
A `Change` object maps to the LDAP protocol of a modify change, and requires you
to set the `operation` and `modification`. The `operation` is a string, and
must be one of:
| Operation | Description |
|-----------|-------------|
| replace | Replaces the attribute referenced in `modification`. If the modification has no values, it is equivalent to a delete. |
| add | Adds the attribute value(s) referenced in `modification`. The attribute may or may not already exist. |
| delete | Deletes the attribute (and all values) referenced in `modification`. |
`modification` is just a plain old JS object with the required type and values you want.
| Operation | Description |
|-----------|-------------|
| type | String that defines the attribute type for the modification. |
| values | Defines the values for modification. |
# modifyDN
`modifyDN(dn, newDN, controls, callback)`
Performs an LDAP modifyDN (rename) operation against an entry in the LDAP
server. A couple points with this client API:
* There is no ability to set "keep old dn." It's always going to flag the old
dn to be purged.
* The client code will automatically figure out if the request is a "new
superior" request ("new superior" means move to a different part of the tree,
as opposed to just renaming the leaf).
Example:
```js
client.modifyDN('cn=foo, o=example', 'cn=bar', (err) => {
assert.ifError(err);
});
```
# search
`search(base, options, controls, callback)`
Performs a search operation against the LDAP server.
The search operation is more complex than the other operations, so this one
takes an `options` object for all the parameters. However, ldapjs makes some
defaults for you so that if you pass nothing in, it's pretty much equivalent
to an HTTP GET operation (i.e., base search against the DN, filter set to
always match).
Like every other operation, `base` is a DN string.
Options can be a string representing a valid LDAP filter or an object
containing the following fields:
|Attribute |Description |
|-----------|---------------------------------------------------|
|scope |One of `base`, `one`, or `sub`. Defaults to `base`.|
|filter |A string version of an LDAP filter (see below), or a programatically constructed `Filter` object. Defaults to `(objectclass=*)`.|
|attributes |attributes to select and return (if these are set, the server will return *only* these attributes). Defaults to the empty set, which means all attributes. You can provide a string if you want a single attribute or an array of string for one or many.|
|attrsOnly |boolean on whether you want the server to only return the names of the attributes, and not their values. Borderline useless. Defaults to false.|
|sizeLimit |the maximum number of entries to return. Defaults to 0 (unlimited).|
|timeLimit |the maximum amount of time the server should take in responding, in seconds. Defaults to 10. Lots of servers will ignore this.|
|paged |enable and/or configure automatic result paging|
Responses inside callback of the `search` method are an `EventEmitter` where you will get a notification for
each `searchEntry` that comes back from the server. You will additionally be able to listen for a `searchRequest`
, `searchReference`, `error` and `end` event.
`searchRequest` is emitted immediately after every `SearchRequest` is sent with a `SearchRequest` parameter. You can do operations
like `client.abandon` with `searchRequest.messageId` to abandon this search request. Note that the `error` event will
only be for client/TCP errors, not LDAP error codes like the other APIs. You'll want to check the LDAP status code
(likely for `0`) on the `end` event to assert success. LDAP search results can give you a lot of status codes, such as
time or size exceeded, busy, inappropriate matching, etc., which is why this method doesn't try to wrap up the code
matching.
Example:
```js
const opts = {
filter: '(&(l=Seattle)(email=*@foo.com))',
scope: 'sub',
attributes: ['dn', 'sn', 'cn']
};
client.search('o=example', opts, (err, res) => {
assert.ifError(err);
res.on('searchRequest', (searchRequest) => {
console.log('searchRequest: ', searchRequest.messageId);
});
res.on('searchEntry', (entry) => {
console.log('entry: ' + JSON.stringify(entry.pojo));
});
res.on('searchReference', (referral) => {
console.log('referral: ' + referral.uris.join());
});
res.on('error', (err) => {
console.error('error: ' + err.message);
});
res.on('end', (result) => {
console.log('status: ' + result.status);
});
});
```
## Filter Strings
The easiest way to write search filters is to write them compliant with RFC2254,
which is "The string representation of LDAP search filters." Note that
ldapjs doesn't support extensible matching, since it's one of those features
that almost nobody actually uses in practice.
Assuming you don't really want to read the RFC, search filters in LDAP are
basically are a "tree" of attribute/value assertions, with the tree specified
in prefix notation. For example, let's start simple, and build up a complicated
filter. The most basic filter is equality, so let's assume you want to search
for an attribute `email` with a value of `foo@bar.com`. The syntax would be:
```
(email=foo@bar.com)
```
ldapjs requires all filters to be surrounded by '()' blocks. Ok, that was easy.
Let's now assume that you want to find all records where the email is actually
just anything in the "@bar.com" domain and the location attribute is set to
Seattle:
```
(&(email=*@bar.com)(l=Seattle))
```
Now our filter is actually three LDAP filters. We have an `and` filter (single
amp `&`), an `equality` filter `(the l=Seattle)`, and a `substring` filter.
Substrings are wildcard filters. They use `*` as the wildcard. You can put more
than one wildcard for a given string. For example you could do `(email=*@*bar.com)`
to match any email of @bar.com or its subdomains like `"example@foo.bar.com"`.
Now, let's say we also want to set our filter to include a
specification that either the employeeType *not* be a manager nor a secretary:
```
(&(email=*@bar.com)(l=Seattle)(!(|(employeeType=manager)(employeeType=secretary))))
```
The `not` character is represented as a `!`, the `or` as a single pipe `|`.
It gets a little bit complicated, but it's actually quite powerful, and lets you
find almost anything you're looking for.
## Paging
Many LDAP server enforce size limits upon the returned result set (commonly
1000). In order to retrieve results beyond this limit, a `PagedResultControl`
is passed between the client and server to iterate through the entire dataset.
While callers could choose to do this manually via the `controls` parameter to
`search()`, ldapjs has internal mechanisms to easily automate the process. The
most simple way to use the paging automation is to set the `paged` option to
true when performing a search:
```js
const opts = {
filter: '(objectclass=commonobject)',
scope: 'sub',
paged: true,
sizeLimit: 200
};
client.search('o=largedir', opts, (err, res) => {
assert.ifError(err);
res.on('searchEntry', (entry) => {
// do per-entry processing
});
res.on('page', (result) => {
console.log('page end');
});
res.on('error', (resErr) => {
assert.ifError(resErr);
});
res.on('end', (result) => {
console.log('done ');
});
});
```
This will enable paging with a default page size of 199 (`sizeLimit` - 1) and
will output all of the resulting objects via the `searchEntry` event. At the
end of each result during the operation, a `page` event will be emitted as
well (which includes the intermediate `searchResult` object).
For those wanting more precise control over the process, an object with several
parameters can be provided for the `paged` option. The `pageSize` parameter
sets the size of result pages requested from the server. If no value is
specified, it will fall back to the default (100 or `sizeLimit` - 1, to obey
the RFC). The `pagePause` parameter allows back-pressure to be exerted on the
paged search operation by pausing at the end of each page. When enabled, a
callback function is passed as an additional parameter to `page` events. The
client will wait to request the next page until that callback is executed.
Here is an example where both of those parameters are used:
```js
const queue = new MyWorkQueue(someSlowWorkFunction);
const opts = {
filter: '(objectclass=commonobject)',
scope: 'sub',
paged: {
pageSize: 250,
pagePause: true
},
};
client.search('o=largerdir', opts, (err, res) => {
assert.ifError(err);
res.on('searchEntry', (entry) => {
// Submit incoming objects to queue
queue.push(entry);
});
res.on('page', (result, cb) => {
// Allow the queue to flush before fetching next page
queue.cbWhenFlushed(cb);
});
res.on('error', (resErr) => {
assert.ifError(resErr);
});
res.on('end', (result) => {
console.log('done');
});
});
```
# starttls
`starttls(options, controls, callback)`
Attempt to secure existing LDAP connection via STARTTLS.
Example:
```js
const opts = {
ca: [fs.readFileSync('mycacert.pem')]
};
client.starttls(opts, (err, res) => {
assert.ifError(err);
// Client communication now TLS protected
});
```
# unbind
`unbind(callback)`
Performs an unbind operation against the LDAP server.
Note that unbind operation is not an opposite operation
for bind. Unbinding results in disconnecting the client
regardless of whether a bind operation was performed.
The `callback` argument is optional as unbind does
not have a response.
Example:
```js
client.unbind((err) => {
assert.ifError(err);
});
```

127
node_modules/ldapjs/docs/dn.md generated vendored Normal file
View File

@ -0,0 +1,127 @@
---
title: DN API | ldapjs
---
# ldapjs DN API
<div class="intro">
This document covers the ldapjs DN API and assumes that you are familiar
with LDAP. If you're not, read the [guide](guide.html) first.
</div>
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.
```js
const parseDN = require('ldapjs').parseDN;
const 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.
```js
server.add('o=example', (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.
```js
server.add('o=example', (req, res, next) => {
const 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.
```js
server.add('o=example', (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).
## format(options)
Convert a DN object to string according to specified formatting options. These
options are divided into two types. Preservation Options use data recorded
during parsing to preserve details of the original DN. Modification options
alter string formatting defaults. Preservation options _always_ take
precedence over Modification Options.
Preservation Options:
- `keepOrder`: Order of multi-value RDNs.
- `keepQuote`: RDN values which were quoted will remain so.
- `keepSpace`: Leading/trailing spaces will be output.
- `keepCase`: Parsed attribute name will be output instead of lowercased version.
Modification Options:
- `upperName`: RDN names will be uppercased instead of lowercased.
- `skipSpace`: Disable trailing space after RDN separators
## setFormat(options)
Sets the default `options` for string formatting when `toString` is called.
It accepts the same parameters as `format`.
## toString()
Returns the string representation of `this`.
```js
server.add('o=example', (req, res, next) => {
console.log(req.dn.toString());
});
```

94
node_modules/ldapjs/docs/errors.md generated vendored Normal file
View File

@ -0,0 +1,94 @@
---
title: Errors API | ldapjs
---
# ldapjs Errors API
<div class="intro">
This document covers the ldapjs errors API and assumes that you are familiar
with LDAP. If you're not, read the [guide](guide.html) first.
</div>
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:
```js
const ldap = require('ldapjs');
const db = {};
server.add('o=example', (req, res, next) => {
const 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

625
node_modules/ldapjs/docs/examples.md generated vendored Normal file
View File

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

317
node_modules/ldapjs/docs/filters.md generated vendored Normal file
View File

@ -0,0 +1,317 @@
---
title: Filters API | ldapjs
---
# ldapjs Filters API
<div class="intro">
This document covers the ldapjs filters API and assumes that you are familiar
with LDAP. If you're not, read the [guide](guide.html) first.
</div>
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.
ldapjs gives you a distinct object type mapping to each filter that is
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,
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:
# 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:
```js
const parseFilter = require('ldapjs').parseFilter;
const f = parseFilter('(objectclass=*)');
```
Is a "simple" filter, and would just return a `PresenceFilter` object. However,
```js
const f = parseFilter('(&(employeeType=manager)(l=Seattle))');
```
Would return an `AndFilter`, which would have a `filters` array of two
`EqualityFilter` objects.
`parseFilter` will throw if an invalid string is passed in (that is, a
syntactically invalid string).
# 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` property 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`.
```js
const f = new EqualityFilter({
attribute: 'cn',
value: 'foo'
});
f.matches({cn: 'foo'}); => true
f.matches({cn: 'bar'}); => false
```
Equality matching uses "strict" type JavaScript comparison, 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`.
```js
const 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:
```js
{
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
```js
const 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`.
```js
const 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`.
```js
const f = new LessThanEqualsFilter({
attribute: 'cn',
value: 'foo',
});
f.matches({cn: 'abc'}); => true
f.matches({cn: 'foobar'}); => 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.
```js
const 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.
```js
const 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.
```js
const 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` property 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`.
```js
const f = new ApproximateFilter({
attribute: 'cn',
value: 'foo'
});
f.matches({cn: 'foo'}); => true
f.matches({cn: 'bar'}); => false
```

697
node_modules/ldapjs/docs/guide.md generated vendored Normal file
View File

@ -0,0 +1,697 @@
---
title: LDAP Guide | ldapjs
---
# LDAP Guide
<div class="intro">
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
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
password."
By the end of this guide, we'll have a simple LDAP server that accomplishes a
"real" task.
</div>
# What exactly is LDAP?
If you haven't already read the
[wikipedia](http://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol)
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 an FS).
* Every entry has a unique name in the tree.
* An entry is a set of attributes.
* An attribute is a key/value(s) pairing (multivalue is natural).
It might be helpful to visualize:
```
o=example
/ \
ou=users ou=groups
/ | | \
cn=john cn=jane cn=dudes cn=dudettes
/
keyid=foo
```
Let's say we wanted to look at the record cn=john:
```shell
dn: cn=john, ou=users, o=example
cn: john
sn: smith
email: john@example.com
email: john.smith@example.com
objectClass: person
```
A few things to note:
* 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
node in the tree, as shown above (the syntax is foo=bar, ...).
* The root of the tree is at the right of the _dn_, which is inverted from a
filesystem hierarchy.
* Every entry in the tree is an _instance of_ an _objectclass_.
* An _objectclass_ is a schema concept; think of it like a table in a
traditional ORM.
* An _objectclass_ defines what _attributes_ an entry can have (on the ORM
analogy, an _attribute_ would be like a column).
That's it. LDAP, then, is the protocol for interacting with the directory tree,
and it's comprehensively specified for common operations, like
add/update/delete and importantly, search. Really, the power of LDAP comes
through the search operations defined in the protocol, which are richer
than HTTP query string filtering, but less powerful than full SQL. You can
think of LDAP as a NoSQL/document store with a well-defined query syntax.
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
lightweight. In particular, almost all of the implementations of LDAP stem
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
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,
when most applications just don't need most of those features.
## How is ldapjs any different?
Well, on the one hand, since ldapjs has to be 100% wire compatible with LDAP to
be useful, it's not. On the other hand, there are no forced assumptions about
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
server implementations support arbitrary "backends" for persistence, but really
you'll be using [BDB](http://www.oracle.com/technetwork/database/berkeleydb/overview/index.html).
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
day or two.
Basically, the ldapjs philosophy is to deal with the "muck" of LDAP, and then
get out of the way so you can just use the "good parts."
# Ok, cool. Learn me some LDAP!
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
/etc/passwd and /etc/group files. Usually sysadmins "go the other way," and
replace /etc/passwd with a
[PAM](http://en.wikipedia.org/wiki/Pluggable_authentication_module "Pluggable
authentication module") module to LDAP. While this is probably not a super
useful real-world use case, it will teach you some of the basics. If it is
useful to you, then that's gravy.
## Install
If you don't already have node.js and npm, clearly you need those, so follow
the steps at [nodejs.org](http://nodejs.org) and [npmjs.org](http://npmjs.org),
respectively. After that, run:
```shell
$ npm install ldapjs
```
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
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
created:
```js
const ldap = require('ldapjs');
const server = ldap.createServer();
server.listen(1389, () => {
console.log('/etc/passwd LDAP server up at: %s', server.url);
});
```
And run that. Doing anything will give you errors (LDAP "No Such Object")
since we haven't added any support in yet, but go ahead and try it anyway:
```shell
$ ldapsearch -H ldap://localhost:1389 -x -b "o=myhost" objectclass=*
```
Before we go any further, note that the complete code for the server we are
about to build up is on the [examples](examples.html) page.
## Bind
So, lesson #1 about LDAP: unlike HTTP, it's connection-oriented; that means that
you authenticate (in LDAP nomenclature this is called a _bind_), and all
subsequent operations operate at the level of priviledge you established during
a bind. You can bind any number of times on a single connection and change that
identity. Technically, it's optional, and you can support _anonymous_
operations from clients, but (1) you probably don't want that, and (2) most
LDAP clients will initiate a bind anyway (OpenLDAP will), so let's add it in
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
has no correspondence to our Unix root user, it's just something we're making up
and going to use for allowing an (LDAP) admin to do anything. To do so, add
this code into your file:
```js
server.bind('cn=root', (req, res, next) => {
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
return next(new ldap.InvalidCredentialsError());
res.end();
return next();
});
```
Not very secure, but this is a demo. What we did there was "mount" a tree in
the ldapjs server, and add a handler for the _bind_ method. If you've ever used
express, this pattern should be really familiar; you can add any number of
handlers in, as we'll see later.
On to the meat of the method. What's up with this?
```js
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
```
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
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.
Hence that check. The second check `req.credentials` is probably obvious, but
it brings up an important point, and that is the `req`, `res` objects in ldapjs
are not homogenous across server operation types. Unlike HTTP, there's not a
single message format, so each of the operations has fields and functions
appropriate to that type. The LDAP bind operation has `credentials`, which are
a string representation of the client's password. This is logically the same as
HTTP Basic Authentication (there are other mechanisms, but that's out of scope
for a getting started guide). Ok, if either of those checks failed, we pass a
new ldapjs `Error` back into the server, and it will (1) halt the chain, and (2)
send the proper error code back to the client.
Lastly, assuming that this request was ok, we just end the operation with
`res.end()`. The `return next()` isn't strictly necessary, since here we only
have one handler in the chain, but it's good habit to always do that, so if you
add another handler in later you won't get bit by it not being invoked.
Blah blah, let's try running the ldap client again, first with a bad password:
```shell
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w foo -b "o=myhost" objectclass=*
ldap_bind: Invalid credentials (49)
matched DN: cn=root
additional info: Invalid Credentials
```
And again with the correct one:
```shell
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" objectclass=*
No such object (32)
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
their CLI less annonyingly noisy. This time, we got another `No such object`
error, but it's for the tree `o=myhost`. That means our bind went through, and
our search failed, since we haven't yet added a search handler. Just one more
small thing to do first.
Remember earlier I said there were no authorization rules baked into LDAP? Well,
we added a bind route, so the only user that can authenticate is `cn=root`, but
what if the remote end doesn't authenticate at all? Right, nothing says they
*have to* bind, that's just what the common clients do. Let's add a quick
authorization handler that we'll use in all our subsequent routes:
```js
function authorize(req, res, next) {
if (!req.connection.ldap.bindDN.equals('cn=root'))
return next(new ldap.InsufficientAccessRightsError());
return next();
}
```
Should be pretty self-explanatory, but as a reminder, LDAP is connection
oriented, so we check that the connection remote user was indeed our `cn=root`
(by default ldapjs will have a DN of `cn=anonymous` if the client didn't bind).
## Search
We said we wanted to allow LDAP operations over /etc/passwd, so let's detour
for a moment to explain an /etc/passwd record.
```shell
jsmith:x:1001:1000:Joe Smith,Room 1007,(234)555-8910,(234)555-0044,email:/home/jsmith:/bin/sh
```
The sample record above maps to:
|Field |Description |
|-------------------|-----------------------------------|
|jsmith |Username |
|x |Placeholder for password hash |
|1001 |Numeric UID |
|1000 |Numeric Primary GID |
|'Joe Smith,...' |DisplayName |
|/home/jsmith |Home directory |
|/bin/sh |Shell |
Let's write some handlers to parse that and transform it into an LDAP search
record (note, you'll need to add `const fs = require('fs');` at the top of the
source file).
First, make a handler that just loads the "user database" in a "pre" handler:
```js
function loadPasswdFile(req, res, next) {
fs.readFile('/etc/passwd', 'utf8', (err, data) => {
if (err)
return next(new ldap.OperationsError(err.message));
req.users = {};
const lines = data.split('\n');
for (const line of lines) {
if (!line || /^#/.test(line))
continue;
const record = line.split(':');
if (!record || !record.length)
continue;
req.users[record[0]] = {
dn: 'cn=' + record[0] + ', ou=users, o=myhost',
attributes: {
cn: record[0],
uid: record[2],
gid: record[3],
description: record[4],
homedirectory: record[5],
shell: record[6] || '',
objectclass: 'unixUser'
}
};
}
return next();
});
}
```
Ok, all that did is tack the /etc/passwd records onto req.users so that any
subsequent handler doesn't have to reload the file. Next, let's write a search
handler to process that:
```js
const pre = [authorize, loadPasswdFile];
server.search('o=myhost', pre, (req, res, next) => {
const keys = Object.keys(req.users);
for (const k of keys) {
if (req.filter.matches(req.users[k].attributes))
res.send(req.users[k]);
}
res.end();
return next();
});
```
And try running:
```shell
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" cn=root
dn: cn=root, ou=users, o=myhost
cn: root
uid: 0
gid: 0
description: System Administrator
homedirectory: /var/root
shell: /bin/sh
objectclass: unixUser
```
Sweet! Try this out too:
```shell
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" objectclass=*
...
```
You should have seen an entry for every record in /etc/passwd with the second.
What all did we do here? A lot. Let's break this down...
### What did I just do on the command line?
Let's start with looking at what you even asked for:
```shell
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" cn=root
```
We can throw away `ldapsearch -H -x -D -w -LLL`, as those just specify the URL
to connect to, the bind credentials and the `-LLL` just quiets down OpenLDAP.
That leaves us with: `-b "o=myhost" cn=root`.
The `-b o=myhost` tells our LDAP server where to _start_ looking in
the tree for entries that might match the search filter, which above is
`cn=root`.
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
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
over the filesystem, we actually would use this, but here, meh.
Next, `cn=root` is the search "filter". LDAP has a rich specification of
filters, where you can specify `and`, `or`, `not`, `>=`, `<=`, `equal`,
`wildcard`, `present` and a few other esoteric things. Really, `equal`,
`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
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'
attribute, which in LDAP parlance is saying "give me everything."
### The code
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
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
of attributes. So that's why we did this:
```js
const entry = {
dn: 'cn=' + record[0] + ', ou=users, o=myhost',
attributes: {
cn: record[0],
uid: record[2],
gid: record[3],
description: record[4],
homedirectory: record[5],
shell: record[6] || '',
objectclass: 'unixUser'
}
};
```
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
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
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'
LDAP server over riak.
To demonstrate what ldapjs is doing for you, let's find all users who have a
shell set to `/bin/false` and whose name starts with `p` (I'm doing this
on Ubuntu). Then, let's say we only care about their login name and primary
group id. We'd do this:
```shell
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" "(&(shell=/bin/false)(cn=p*))" cn gid
dn: cn=proxy, ou=users, o=myhost
cn: proxy
gid: 13
dn: cn=pulse, ou=users, o=myhost
cn: pulse
gid: 114
```
## Add
This is going to be a little bit ghetto, since what we're going to do is just
use node's child process module to spawn calls to `adduser`. Go ahead and add
the following code in as another handler (you'll need a
`const { spawn } = require('child_process');` at the top of your file):
```js
server.add('ou=users, o=myhost', pre, (req, res, next) => {
if (!req.dn.rdns[0].attrs.cn)
return next(new ldap.ConstraintViolationError('cn required'));
if (req.users[req.dn.rdns[0].attrs.cn.value])
return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
const entry = req.toObject().attributes;
if (entry.objectclass.indexOf('unixUser') === -1)
return next(new ldap.ConstraintViolationError('entry must be a unixUser'));
const opts = ['-m'];
if (entry.description) {
opts.push('-c');
opts.push(entry.description[0]);
}
if (entry.homedirectory) {
opts.push('-d');
opts.push(entry.homedirectory[0]);
}
if (entry.gid) {
opts.push('-g');
opts.push(entry.gid[0]);
}
if (entry.shell) {
opts.push('-s');
opts.push(entry.shell[0]);
}
if (entry.uid) {
opts.push('-u');
opts.push(entry.uid[0]);
}
opts.push(entry.cn[0]);
const useradd = spawn('useradd', opts);
const messages = [];
useradd.stdout.on('data', (data) => {
messages.push(data.toString());
});
useradd.stderr.on('data', (data) => {
messages.push(data.toString());
});
useradd.on('exit', (code) => {
if (code !== 0) {
let msg = '' + code;
if (messages.length)
msg += ': ' + messages.join();
return next(new ldap.OperationsError(msg));
}
res.end();
return next();
});
});
```
Then, you'll need to be root to have this running, so start your server with
`sudo` (or be root, whatever). Now, go ahead and create a file called
`user.ldif` with the following contents:
```shell
dn: cn=ldapjs, ou=users, o=myhost
objectClass: unixUser
cn: ldapjs
shell: /bin/bash
description: Created via ldapadd
```
Now go ahead and invoke with:
```shell
$ ldapadd -H ldap://localhost:1389 -x -D cn=root -w secret -f ./user.ldif
adding new entry "cn=ldapjs, ou=users, o=myhost"
```
Let's confirm he got added with an ldapsearch:
```shell
$ ldapsearch -H ldap://localhost:1389 -LLL -x -D cn=root -w secret -b "ou=users, o=myhost" cn=ldapjs
dn: cn=ldapjs, ou=users, o=myhost
cn: ldapjs
uid: 1001
gid: 1001
description: Created via ldapadd
homedirectory: /home/ldapjs
shell: /bin/bash
objectclass: unixUser
```
As before, here's a breakdown of the code:
```js
server.add('ou=users, o=myhost', pre, (req, res, next) => {
if (!req.dn.rdns[0].attrs.cn)
return next(new ldap.ConstraintViolationError('cn required'));
if (req.users[req.dn.rdns[0].attrs.cn.value])
return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
const entry = req.toObject().attributes;
if (entry.objectclass.indexOf('unixUser') === -1)
return next(new ldap.ConstraintViolationError('entry must be a unixUser'));
});
```
A few new things:
* 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
different part of the tree.
* We did some really minimal schema enforcement by:
+ Checking that the leaf RDN (relative distinguished name) was a _cn_
attribute.
+ We then did `req.toObject()`. As mentioned before, each of the req/res
objects have special APIs that make sense for that operation. Without getting
into the details, the LDAP add operation on the wire doesn't look like a JS
object, and we want to support both the LDAP nerd that wants to see what
got sent, and the "easy" case. So use `.toObject()`. Note we also filtered
out to the `attributes` portion of the object since that's all we're really
looking at.
+ Lastly, we did a super minimal check to see if the entry was of type
`unixUser`. Frankly for this case, it's kind of useless, but it does illustrate
one point: attribute names are case-insensitive, so ldapjs converts them all to
lower case (note the client sent _objectClass_ over the wire).
After that, we really just delegated off to the _useradd_ command. As far as I
know, there is not a node.js module that wraps up `getpwent` and friends,
otherwise we'd use that.
Now, what's missing? Oh, right, we need to let you set a password. Well, let's
support that via the _modify_ command.
## Modify
Unlike HTTP, "partial" document updates are fully specified as part of the
RFC, so appending, removing, or replacing a single attribute is pretty natural.
Go ahead and add the following code into your source file:
```js
server.modify('ou=users, o=myhost', pre, (req, res, next) => {
if (!req.dn.rdns[0].attrs.cn || !req.users[req.dn.rdns[0].attrs.cn.value])
return next(new ldap.NoSuchObjectError(req.dn.toString()));
if (!req.changes.length)
return next(new ldap.ProtocolError('changes required'));
const user = req.users[req.dn.rdns[0].attrs.cn.value].attributes;
let mod;
for (const i = 0; i < req.changes.length; i++) {
mod = req.changes[i].modification;
switch (req.changes[i].operation) {
case 'replace':
if (mod.type !== 'userpassword' || !mod.vals || !mod.vals.length)
return next(new ldap.UnwillingToPerformError('only password updates ' +
'allowed'));
break;
case 'add':
case 'delete':
return next(new ldap.UnwillingToPerformError('only replace allowed'));
}
}
const passwd = spawn('chpasswd', ['-c', 'MD5']);
passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8');
passwd.on('exit', (code) => {
if (code !== 0)
return next(new ldap.OperationsError(code));
res.end();
return next();
});
});
```
Basically, we made sure the remote client was targeting an entry that exists,
ensuring that they were asking to "replace" the `userPassword` attribute (which
is the 'standard' LDAP attribute for passwords; if you think it's easier to use
'password', knock yourself out), and then just delegating to the `chpasswd`
command (which lets you change a user's password over stdin). Next, go ahead
and create a `passwd.ldif` file:
```shell
dn: cn=ldapjs, ou=users, o=myhost
changetype: modify
replace: userPassword
userPassword: secret
-
```
And then run the OpenLDAP CLI:
```shell
$ ldapmodify -H ldap://localhost:1389 -x -D cn=root -w secret -f ./passwd.ldif
```
You should now be able to login to your box as the ldapjs user. Let's get
the last "mainline" piece of work out of the way, and delete the user.
## Delete
Delete is pretty straightforward. The client gives you a dn to delete, and you
delete it :). Add the following code into your server:
```js
server.del('ou=users, o=myhost', pre, (req, res, next) => {
if (!req.dn.rdns[0].attrs.cn || !req.users[req.dn.rdns[0].attrs.cn.value])
return next(new ldap.NoSuchObjectError(req.dn.toString()));
const userdel = spawn('userdel', ['-f', req.dn.rdns[0].attrs.cn.value]);
const messages = [];
userdel.stdout.on('data', (data) => {
messages.push(data.toString());
});
userdel.stderr.on('data', (data) => {
messages.push(data.toString());
});
userdel.on('exit', (code) => {
if (code !== 0) {
let msg = '' + code;
if (messages.length)
msg += ': ' + messages.join();
return next(new ldap.OperationsError(msg));
}
res.end();
return next();
});
});
```
And then run the following command:
```shell
$ ldapdelete -H ldap://localhost:1389 -x -D cn=root -w secret "cn=ldapjs, ou=users, o=myhost"
```
# 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.

95
node_modules/ldapjs/docs/index.md generated vendored Normal file
View File

@ -0,0 +1,95 @@
---
title: ldapjs
---
<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
<div class="intro">
ldapjs is a pure JavaScript, from-scratch framework for implementing
[LDAP](http://tools.ietf.org/html/rfc4510) clients and servers in
[Node.js](http://nodejs.org). It is intended for developers used to interacting
with HTTP services in node and [restify](http://restify.com).
</div>
```js
const ldap = require('ldapjs');
const server = ldap.createServer();
server.search('o=example', (req, res, next) => {
const obj = {
dn: req.dn.toString(),
attributes: {
objectclass: ['organization', 'top'],
o: 'example'
}
};
if (req.filter.matches(obj.attributes))
res.send(obj);
res.end();
});
server.listen(1389, () => {
console.log('LDAP server listening at %s', server.url);
});
```
Try hitting that with:
```shell
$ ldapsearch -H ldap://localhost:1389 -x -b o=example objectclass=*
```
# Features
ldapjs implements most of the common operations in the LDAP v3 RFC(s), for
both client and server. It is 100% wire-compatible with the LDAP protocol
itself, and is interoperable with [OpenLDAP](http://openldap.org) and any other
LDAPv3-compliant implementation. ldapjs gives you a powerful routing and
"intercepting filter" pattern for implementing server(s). It is intended
that you can build LDAP over anything you want, not just traditional databases.
# Getting started
```shell
$ npm install ldapjs
```
If you're new to LDAP, check out the [guide](guide.html). Otherwise, the
API documentation is:
|Section |Content |
|---------------------------|-------------------------------------------|
|[Server API](server.html) |Reference for implementing LDAP servers. |
|[Client API](client.html) |Reference for implementing LDAP clients. |
|[DN API](dn.html) |API reference for the DN class. |
|[Filter API](filters.html) |API reference for LDAP search filters. |
|[Error API](errors.html) |Listing of all ldapjs Error objects. |
|[Examples](examples.html) |Collection of sample/getting started code. |
# More information
- License:[MIT](http://opensource.org/licenses/mit-license.php)
- Code: [ldapjs/node-ldapjs](https://github.com/ldapjs/node-ldapjs)
# What's not in the box?
Since most developers and system(s) adminstrators struggle with some of the
esoteric features of LDAP, not all features in LDAP are implemented here.
Specifically:
* LDIF
* Aliases
* Attributes by OID
* Extensible matching
There are a few others, but those are the "big" ones.

614
node_modules/ldapjs/docs/server.md generated vendored Normal file
View File

@ -0,0 +1,614 @@
---
title: Server API | ldapjs
---
# ldapjs Server API
<div class="intro">
This document covers the ldapjs server API and assumes that you are familiar
with LDAP. If you're not, read the [guide](guide.html) first.
</div>
# Create a server
The code to create a new server looks like:
```js
const server = ldap.createServer();
```
The full list of options is:
||log||You can optionally pass in a Bunyan compatible logger instance the client will use to acquire a child logger.||
||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.||
### Note On Logger
The passed in logger is expected to conform to the Log4j standard API.
Internally, [abstract-logging](https://www.npmjs.com/packages/abstract-logging) is
used to implement the interface. As a result, no log messages will be generated
unless an external logger is supplied.
Known compatible loggers are:
+ [Bunyan](https://www.npmjs.com/package/bunyan)
+ [Pino](https://www.npmjs.com/package/pino)
## Properties on the server object
### maxConnections
Set this property to reject connections when the server's connection count gets
high.
### connections (getter only) - DEPRECATED
The number of concurrent connections on the server. This property is deprecated,
please use server.getConnections() instead.
### 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.js `server.listen` family of
APIs.
After calling `listen`, the property `url` on the server object itself will be
available.
Example:
```js
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 the IPv4 address
`127.0.0.1`. To listen on any other address, supply said address as the `host`
parameter. For example, to listen on all available IPv6 addresses supply
`::` as the `host` (note, this _may_ also result in listening on all
available IPv4 addresses, depending on operating system behavior).
We highly recommend being as explicit as possible with the `host` parameter.
Listening on all available addresses (through `::` or `0.0.0.0`) can lead
to potential security issues.
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)`.
## Inspecting server state
### server.getConnections(callback)
The LDAP server API mirrors the [Node.js `server.getConnections` API](https://nodejs.org/dist/latest-v12.x/docs/api/net.html#net_server_getconnections_callback). Callback
should take two arguments err and count.
# Routes
The LDAP server API is meant to be the LDAP-equivalent of the express/restify
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:
```js
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](https://github.com/basho/riak). Getting an LDAP server up with riak
looks like:
```js
const ldap = require('ldapjs');
const ldapRiak = require('ldapjs-riak');
const server = ldap.createServer();
const backend = ldapRiak.createBackend({
"host": "localhost",
"port": 8098,
"bucket": "example",
"indexes": ["l", "cn"],
"uniqueIndexes": ["uid"],
"numConnections": 5
});
server.add("o=example",
backend,
backend.add());
...
```
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. 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 heterogeneous wire format, so each
operation requires specific methods/fields on the request/response
objects. However, there is a `.use()` method availabe, similar to
that on express/connect, allowing you to chain up "middleware":
```js
server.use(function(req, res, next) {
console.log('hello world');
return next();
});
```
## Common Request Elements
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
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 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:
```js
server.search('o=example',
(req, res, next) => { return next(); },
(req, res, next) => { return next(new ldap.OperationsError()); },
(req, res, next) => { res.end(); }
);
```
In the code snipped above, the third handler would never get invoked.
# Bind
Adds a mount in the tree to perform LDAP binds with. Example:
```js
server.bind('ou=people, o=example', (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.
```js
server.add('ou=people, o=example', (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 (this is the same as the `dn`
property).
### attributes
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
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:
```js
{
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.
```js
server.search('o=example', (req, res, next) => {
console.log('base object: ' + req.dn.toString());
console.log('scope: ' + req.scope);
console.log('filter: ' + req.filter.toString());
res.end();
});
```
## 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
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'. ldapjs will take care of this for you.
### filter
The [filter](filters.html) object that the client requested. Notably this has
a `matches()` method 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. 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()`.
```js
server.search('o=example', (req, res, next) => {
const 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.
```js
server.modify('o=example', (req, res, next) => {
console.log('DN: ' + req.dn.toString());
console.log('changes:');
for (const c of req.changes) {
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 (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', or '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.
```js
server.del('o=example', (req, res, next) => {
console.log('DN: ' + req.dn.toString());
res.end();
});
```
## DeleteRequest
### entry
The DN the client is attempting to delete (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.
```js
server.compare('o=example', (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 (this is the same as the `dn`
property).
### attribute
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
(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.
```js
server.modifyDN('o=example', (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 (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
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.
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.
```js
// LDAP whoami
server.exop('1.3.6.1.4.1.4203.1.11.3', (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
Will always be a match to the route-defined name. Clients must include 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, or perform any other cleanup
tasks you need to.
```js
server.unbind((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.