First commit
This commit is contained in:
1
node_modules/ldapjs/docs/branding/public/CNAME
generated
vendored
Normal file
1
node_modules/ldapjs/docs/branding/public/CNAME
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
ldapjs.org
|
||||
266
node_modules/ldapjs/docs/branding/public/media/css/style.css
generated
vendored
Normal file
266
node_modules/ldapjs/docs/branding/public/media/css/style.css
generated
vendored
Normal 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;
|
||||
}
|
||||
1
node_modules/ldapjs/docs/branding/public/media/img/logo.svg
generated
vendored
Normal file
1
node_modules/ldapjs/docs/branding/public/media/img/logo.svg
generated
vendored
Normal 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
39
node_modules/ldapjs/docs/branding/template.html
generated
vendored
Normal 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
491
node_modules/ldapjs/docs/client.md
generated
vendored
Normal 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
127
node_modules/ldapjs/docs/dn.md
generated
vendored
Normal 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
94
node_modules/ldapjs/docs/errors.md
generated
vendored
Normal 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
625
node_modules/ldapjs/docs/examples.md
generated
vendored
Normal 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
317
node_modules/ldapjs/docs/filters.md
generated
vendored
Normal 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
697
node_modules/ldapjs/docs/guide.md
generated
vendored
Normal 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
95
node_modules/ldapjs/docs/index.md
generated
vendored
Normal 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
614
node_modules/ldapjs/docs/server.md
generated
vendored
Normal 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.
|
||||
Reference in New Issue
Block a user