First commit

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

4
node_modules/ldapjs/.eslintignore generated vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
coverage/
.nyc_output/
docs/

20
node_modules/ldapjs/.eslintrc.js generated vendored Normal file
View File

@ -0,0 +1,20 @@
module.exports = {
env: {
commonjs: true,
es2021: true,
node: true
},
parserOptions: {
ecmaVersion: 'latest'
},
extends: [
'standard'
],
rules: {
'no-shadow': 'error',
'no-unused-vars': ['error', {
argsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_'
}]
}
}

18
node_modules/ldapjs/.github/dependabot.yml generated vendored Normal file
View File

@ -0,0 +1,18 @@
version: 2
updates:
- package-ecosystem: "github-actions"
# versioning-strategy: increase-if-necessary
directory: "/"
schedule:
interval: "weekly"
day: "saturday"
time: "03:00"
timezone: "America/New_York"
- package-ecosystem: "npm"
versioning-strategy: increase-if-necessary
directory: "/"
schedule:
interval: "weekly"
day: "saturday"
time: "03:00"
timezone: "America/New_York"

30
node_modules/ldapjs/.github/workflows/docs.yml generated vendored Normal file
View File

@ -0,0 +1,30 @@
name: 'Update Docs'
on:
push:
branches:
- master
jobs:
docs:
name: Update Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install Packages
run: npm install
- name: Build Docs
run: npm run docs
- name: Deploy 🚢
uses: cpina/github-action-push-to-another-repository@master
env:
API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }}
with:
source-directory: 'public'
destination-github-username: 'ldapjs'
destination-repository-name: 'ldapjs.github.io'
user-email: 'bot@ldapjs.org'
target-branch: 'gh-pages'

39
node_modules/ldapjs/.github/workflows/integration.yml generated vendored Normal file
View File

@ -0,0 +1,39 @@
name: 'Integration Tests'
# Notes:
# https://github.community/t5/GitHub-Actions/Github-Actions-services-not-reachable/m-p/30739/highlight/true#M538
on:
push:
branches:
- master
- next
pull_request:
branches:
- master
- next
jobs:
baseline:
name: Baseline Tests
runs-on: ubuntu-latest
services:
openldap:
image: ghcr.io/ldapjs/docker-test-openldap/openldap:2023-10-30
ports:
- 389:389
- 636:636
options: >
--health-cmd "ldapsearch -Y EXTERNAL -Q -H ldapi:// -b ou=people,dc=planetexpress,dc=com -LLL '(cn=Turanga Leela)' cn"
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 'lts/*'
- name: Install Packages
run: npm install
- name: Run Tests
run: npm run test:integration

47
node_modules/ldapjs/.github/workflows/main.yml generated vendored Normal file
View File

@ -0,0 +1,47 @@
name: 'Lint And Test'
on:
push:
branches:
- master
- next
pull_request:
branches:
- master
- next
jobs:
lint:
name: Lint Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 'lts/*'
- name: Install Packages
run: npm install
- name: Lint Code
run: npm run lint:ci
run_tests:
name: Unit Tests
strategy:
matrix:
os:
- ubuntu-latest
- windows-latest
node:
- 16
- 18
- 20
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- name: Install Packages
run: npm install
- name: Run Tests
run: npm run test:ci

10
node_modules/ldapjs/.taprc.yml generated vendored Normal file
View File

@ -0,0 +1,10 @@
# With PR #834 the code in this code base has been reduced significantly.
# As a result, the coverage percentages changed, and are much lower than
# previously. So we are reducing the requirements accordingly
branches: 50
functions: 50
lines: 50
statements: 50
files:
- 'test/**/*.test.js'

104
node_modules/ldapjs/CHANGES.md generated vendored Normal file
View File

@ -0,0 +1,104 @@
# ldapjs Changelog
## 2.0.0
- Going foward, please see https://github.com/ldapjs/node-ldapjs/releases
## 1.0.2
- Update dtrace-provider dependency
## 1.0.1
- Update dependencies
* assert-plus to 1.0.0
* bunyan to 1.8.3
* dashdash to 1.14.0
* backoff to 2.5.0
* once to 1.4.0
* vasync to 1.6.4
* verror to 1.8.1
* dtrace-provider to 0.7.0
- Drop any semblence of support for node 0.8.x
## 1.0.0
- Update dependencies
* asn1 to 0.2.3
* bunyan to 1.5.1
* dtrace-provider to 0.6.0
- Removed pooled client
- Removed custom formatting for GUIDs
- Completely overhaul DN parsing/formatting
- Add options for format preservation
- Removed `spaced()` and `rndSpaced` from DN API
- Fix parent/child rules regarding empty DNs
- Request routing overhaul
* #154 Route lookups do not depend on object property order
* #111 Null ('') DN will act as catch-all
- Add StartTLS support to client (Sponsored by: DoubleCheck Email Manager)
- Improve robustness of client reconnect logic
- Add 'resultError' event to client
- Update paged search automation in client
- Add Change.apply method for modifying objects
- #143 Preserve raw Buffer value in Control objects
- Test code coverage with node-istanbul
- Convert tests to node-tape
- Add controls for server-side sorting
- #201 Replace nopt with dashdash
- #134 Allow configuration of derefAliases client option
- #197 Properly dispatch unbind requests
- #196 Handle string ports properly in server.listen
- Add basic server API tests
- Store EqualityFilter value as Buffer
- Run full test suite during 'make test'
- #190 Add error code 123 from RFC4370
- #178 Perform strict presence testing on attribute vals
- #183 Accept buffers or strings for cert/key in createServer
- #180 Add '-i, --insecure' option and to all ldapjs-\* CLIs
- #254 Allow simple client bind with empty credentials
## 0.7.1
- #169 Update dependencies
* asn1 to 0.2.1
* pooling to 0.4.6
* assert-plus to 0.1.5
* bunyan to 0.22.1
- #173 Make dtrace-provider an optional dependency
- #142 Improve parser error handling
- #161 Properly handle close events on tls sockets
- #163 Remove buffertools dependency
- #162 Fix error event handling for pooled clients
- #159 Allow ext request message to have a buffer value
- #155 Make \*Filter.matches case insensitive for attrs
## 0.7.0
- #87 Minor update to ClientPool event pass-through
- #145 Update pooling to 0.4.5
- #144 Fix unhandled error during client connection
- Output ldapi:// URLs for UNIX domain sockets
- Support extensible matching of caseIgnore and caseIgnoreSubstrings
- Fix some ClientPool event handling
- Improve DN formatting flexibility
* Add 'spaced' function to DN objects allowing toggle of inter-RDN when
rendering to a string. ('dc=test,dc=tld' vs 'dc=test, dc=tld')
* Detect RDN spacing when parsing DN.
- #128 Fix user can't bind with inmemory example
- #139 Bump required tap version to 0.4.1
- Allow binding ldap server on an ephemeral port
## 0.6.3
- Update bunyan to 0.21.1
- Remove listeners on the right object (s/client/res/)
- Replace log4js with bunyan for binaries
- #127 socket is closed issue with pools
- #122 Allow changing TLS connection options in client
- #120 Fix a bug with formatting digits less than 16.
- #118 Fix "failed to instantiate provider" warnings in console on SmartOS
## 0.6.2 - 0.1.0
**See git history**

19
node_modules/ldapjs/LICENSE generated vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2019 LDAPjs, All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE

71
node_modules/ldapjs/README.md generated vendored Normal file
View File

@ -0,0 +1,71 @@
# LDAPjs
[![Build Status](https://github.com/ldapjs/node-ldapjs/workflows/Lint%20And%20Test/badge.svg)](https://github.com/ldapjs/node-ldapjs/actions)
[![Coverage Status](https://coveralls.io/repos/github/ldapjs/node-ldapjs/badge.svg)](https://coveralls.io/github/ldapjs/node-ldapjs/)
LDAPjs makes the LDAP protocol a first class citizen in Node.js.
## Usage
For full docs, head on over to <http://ldapjs.org>.
```javascript
var ldap = require('ldapjs');
var server = ldap.createServer();
server.search('dc=example', function(req, res, next) {
var 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, function() {
console.log('ldapjs listening at ' + server.url);
});
```
To run that, assuming you've got the [OpenLDAP](http://www.openldap.org/)
client on your system:
ldapsearch -H ldap://localhost:1389 -x -b dc=example objectclass=*
## Installation
npm install ldapjs
## Node.js Version Support
As of `ldapjs@3` we only support the active Node.js LTS releases.
See [https://github.com/nodejs/release#release-schedule][schedule] for the LTS
release schedule.
For a definitive list of Node.js version we support, see the version matrix
we test against in our [CI configuration][ci-config].
Note: given the release date of `ldapjs@3`, and the short window of time that
Node.js v14 had remaining on its LTS window, we opted to not support Node.js
v14 with `ldapjs@3` (we released late February 2023 and v14 goes into
maintenance in late April 2023). Also, Node.js v14 will be end-of-life (EOL) on
September 11, 2023; this is a very shortened EOL timeline and makes it even
more reasonable to not support it at this point.
[schedule]: https://github.com/nodejs/release#release-schedule
[ci-config]: https://github.com/ldapjs/node-ldapjs/blob/master/.github/workflows/main.yml
## License
MIT.
## Bugs
See <https://github.com/ldapjs/node-ldapjs/issues>.

10
node_modules/ldapjs/docker-compose.yml generated vendored Normal file
View File

@ -0,0 +1,10 @@
services:
openldap:
image: ghcr.io/ldapjs/docker-test-openldap/openldap:2023-10-30
ports:
- 389:389
- 636:636
healthcheck:
start_period: 3s
test: >
/usr/bin/ldapsearch -Y EXTERNAL -Q -H ldapi:// -b ou=people,dc=planetexpress,dc=com -LLL '(cn=Turanga Leela)' cn 1>/dev/null

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

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

View File

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

View File

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

After

Width:  |  Height:  |  Size: 3.2 KiB

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

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

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

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

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

@ -0,0 +1,127 @@
---
title: DN API | ldapjs
---
# ldapjs DN API
<div class="intro">
This document covers the ldapjs DN API and assumes that you are familiar
with LDAP. If you're not, read the [guide](guide.html) first.
</div>
DNs are LDAP distinguished names, and are composed of a set of RDNs (relative
distinguished names). [RFC2253](http://www.ietf.org/rfc/rfc2253.txt) has the
complete specification, but basically an RDN is an attribute value assertion
with `=` as the seperator, like: `cn=foo` where 'cn' is 'commonName' and 'foo'
is the value. You can have compound RDNs by using the `+` character:
`cn=foo+sn=bar`. As stated above, DNs are a set of RDNs, typically separated
with the `,` character, like: `cn=foo, ou=people, o=example`. This uniquely
identifies an entry in the tree, and is read "bottom up".
# parseDN(dnString)
The `parseDN` API converts a string representation of a DN into an ldapjs DN
object; in most cases this will be handled for you under the covers of the
ldapjs framework, but if you need it, it's there.
```js
const parseDN = require('ldapjs').parseDN;
const dn = parseDN('cn=foo+sn=bar, ou=people, o=example');
console.log(dn.toString());
```
# DN
The DN object is largely what you'll be interacting with, since all the server
APIs are setup to give you a DN object.
## childOf(dn)
Returns a boolean indicating whether 'this' is a child of the passed in dn. The
`dn` argument can be either a string or a DN.
```js
server.add('o=example', (req, res, next) => {
if (req.dn.childOf('ou=people, o=example')) {
...
} else {
...
}
});
```
## parentOf(dn)
The inverse of `childOf`; returns a boolean on whether or not `this` is a parent
of the passed in dn. Like `childOf`, can take either a string or a DN.
```js
server.add('o=example', (req, res, next) => {
const dn = parseDN('ou=people, o=example');
if (dn.parentOf(req.dn)) {
...
} else {
...
}
});
```
## equals(dn)
Returns a boolean indicating whether `this` is equivalent to the passed in `dn`
argument. `dn` can be a string or a DN.
```js
server.add('o=example', (req, res, next) => {
if (req.dn.equals('cn=foo, ou=people, o=example')) {
...
} else {
...
}
});
```
## parent()
Returns a DN object that is the direct parent of `this`. If there is no parent
this can return `null` (e.g. `parseDN('o=example').parent()` will return null).
## format(options)
Convert a DN object to string according to specified formatting options. These
options are divided into two types. Preservation Options use data recorded
during parsing to preserve details of the original DN. Modification options
alter string formatting defaults. Preservation options _always_ take
precedence over Modification Options.
Preservation Options:
- `keepOrder`: Order of multi-value RDNs.
- `keepQuote`: RDN values which were quoted will remain so.
- `keepSpace`: Leading/trailing spaces will be output.
- `keepCase`: Parsed attribute name will be output instead of lowercased version.
Modification Options:
- `upperName`: RDN names will be uppercased instead of lowercased.
- `skipSpace`: Disable trailing space after RDN separators
## setFormat(options)
Sets the default `options` for string formatting when `toString` is called.
It accepts the same parameters as `format`.
## toString()
Returns the string representation of `this`.
```js
server.add('o=example', (req, res, next) => {
console.log(req.dn.toString());
});
```

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

@ -0,0 +1,94 @@
---
title: Errors API | ldapjs
---
# ldapjs Errors API
<div class="intro">
This document covers the ldapjs errors API and assumes that you are familiar
with LDAP. If you're not, read the [guide](guide.html) first.
</div>
All errors in the ldapjs framework extend from an abstract error type called
`LDAPError`. In addition to the properties listed below, all errors will have
a `stack` property correctly set.
In general, you'll be using the errors in ldapjs like:
```js
const ldap = require('ldapjs');
const db = {};
server.add('o=example', (req, res, next) => {
const parent = req.dn.parent();
if (parent) {
if (!db[parent.toString()])
return next(new ldap.NoSuchObjectError(parent.toString()));
}
if (db[req.dn.toString()])
return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
...
});
```
I.e., if you just pass them into the `next()` handler, ldapjs will automatically
return the appropriate LDAP error message, and stop the handler chain.
All errors will have the following properties:
## code
Returns the LDAP status code associated with this error.
## name
The name of this error.
## message
The message that will be returned to the client.
# Complete list of LDAPError subclasses
* OperationsError
* ProtocolError
* TimeLimitExceededError
* SizeLimitExceededError
* CompareFalseError
* CompareTrueError
* AuthMethodNotSupportedError
* StrongAuthRequiredError
* ReferralError
* AdminLimitExceededError
* UnavailableCriticalExtensionError
* ConfidentialityRequiredError
* SaslBindInProgressError
* NoSuchAttributeError
* UndefinedAttributeTypeError
* InappropriateMatchingError
* ConstraintViolationError
* AttributeOrValueExistsError
* InvalidAttriubteSyntaxError
* NoSuchObjectError
* AliasProblemError
* InvalidDnSyntaxError
* AliasDerefProblemError
* InappropriateAuthenticationError
* InvalidCredentialsError
* InsufficientAccessRightsError
* BusyError
* UnavailableError
* UnwillingToPerformError
* LoopDetectError
* NamingViolationError
* ObjectclassViolationError
* NotAllowedOnNonLeafError
* NotAllowedOnRdnError
* EntryAlreadyExistsError
* ObjectclassModsProhibitedError
* AffectsMultipleDsasError
* OtherError

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

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

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

@ -0,0 +1,317 @@
---
title: Filters API | ldapjs
---
# ldapjs Filters API
<div class="intro">
This document covers the ldapjs filters API and assumes that you are familiar
with LDAP. If you're not, read the [guide](guide.html) first.
</div>
LDAP search filters are really the backbone of LDAP search operations, and
ldapjs tries to get you in "easy" with them if your dataset is small, and also
lets you introspect them if you want to write a "query planner". For reference,
make sure to read over [RFC2254](http://www.ietf.org/rfc/rfc2254.txt), as this
explains the LDAPv3 text filter representation.
ldapjs gives you a distinct object type mapping to each filter that is
context-sensitive. However, _all_ filters have a `matches()` method on them, if
that's all you need. Most filters will have an `attribute` property on them,
since "simple" filters all operate on an attribute/value assertion. The
"complex" filters are really aggregations of other filters (i.e. 'and'), and so
these don't provide that property.
All Filters in the ldapjs framework extend from `Filter`, which wil have the
property `type` available; this will return a string name for the filter, and
will be one of:
# parseFilter(filterString)
Parses an [RFC2254](http://www.ietf.org/rfc/rfc2254.txt) filter string into an
ldapjs object(s). If the filter is "complex", it will be a "tree" of objects.
For example:
```js
const parseFilter = require('ldapjs').parseFilter;
const f = parseFilter('(objectclass=*)');
```
Is a "simple" filter, and would just return a `PresenceFilter` object. However,
```js
const f = parseFilter('(&(employeeType=manager)(l=Seattle))');
```
Would return an `AndFilter`, which would have a `filters` array of two
`EqualityFilter` objects.
`parseFilter` will throw if an invalid string is passed in (that is, a
syntactically invalid string).
# EqualityFilter
The equality filter is used to check exact matching of attribute/value
assertions. This object will have an `attribute` and `value` property, and the
`name` property will be `equal`.
The string syntax for an equality filter is `(attr=value)`.
The `matches()` method will return true IFF the passed in object has a
key matching `attribute` and a value matching `value`.
```js
const f = new EqualityFilter({
attribute: 'cn',
value: 'foo'
});
f.matches({cn: 'foo'}); => true
f.matches({cn: 'bar'}); => false
```
Equality matching uses "strict" type JavaScript comparison, and by default
everything in ldapjs (and LDAP) is a UTF-8 string. If you want comparison
of numbers, or something else, you'll need to use a middleware interceptor
that transforms values of objects.
# PresenceFilter
The presence filter is used to check if an object has an attribute at all, with
any value. This object will have an `attribute` property, and the `name`
property will be `present`.
The string syntax for a presence filter is `(attr=*)`.
The `matches()` method will return true IFF the passed in object has a
key matching `attribute`.
```js
const f = new PresenceFilter({
attribute: 'cn'
});
f.matches({cn: 'foo'}); => true
f.matches({sn: 'foo'}); => false
```
# SubstringFilter
The substring filter is used to do wildcard matching of a string value. This
object will have an `attribute` property and then it will have an `initial`
property, which is the prefix match, an `any` which will be an array of strings
that are to be found _somewhere_ in the target string, and a `final` property,
which will be the suffix match of the string. `any` and `final` are both
optional. The `name` property will be `substring`.
The string syntax for a presence filter is `(attr=foo*bar*cat*dog)`, which would
map to:
```js
{
initial: 'foo',
any: ['bar', 'cat'],
final: 'dog'
}
```
The `matches()` method will return true IFF the passed in object has a
key matching `attribute` and the "regex" matches the value
```js
const f = new SubstringFilter({
attribute: 'cn',
initial: 'foo',
any: ['bar'],
final: 'baz'
});
f.matches({cn: 'foobigbardogbaz'}); => true
f.matches({sn: 'fobigbardogbaz'}); => false
```
# GreaterThanEqualsFilter
The ge filter is used to do comparisons and ordering based on the value type. As
mentioned elsewhere, by default everything in LDAP and ldapjs is a string, so
this filter's `matches()` would be using lexicographical ordering of strings.
If you wanted `>=` semantics over numeric values, you would need to add some
middleware to convert values before comparison (and the value of the filter).
Note that the ldapjs schema middleware will do this.
The GreaterThanEqualsFilter will have an `attribute` property, a `value`
property and the `name` property will be `ge`.
The string syntax for a ge filter is:
```
(cn>=foo)
```
The `matches()` method will return true IFF the passed in object has a
key matching `attribute` and the value is `>=` this filter's `value`.
```js
const f = new GreaterThanEqualsFilter({
attribute: 'cn',
value: 'foo',
});
f.matches({cn: 'foobar'}); => true
f.matches({cn: 'abc'}); => false
```
# LessThanEqualsFilter
The le filter is used to do comparisons and ordering based on the value type. As
mentioned elsewhere, by default everything in LDAP and ldapjs is a string, so
this filter's `matches()` would be using lexicographical ordering of strings.
If you wanted `<=` semantics over numeric values, you would need to add some
middleware to convert values before comparison (and the value of the filter).
Note that the ldapjs schema middleware will do this.
The string syntax for a le filter is:
```
(cn<=foo)
```
The LessThanEqualsFilter will have an `attribute` property, a `value`
property and the `name` property will be `le`.
The `matches()` method will return true IFF the passed in object has a
key matching `attribute` and the value is `<=` this filter's `value`.
```js
const f = new LessThanEqualsFilter({
attribute: 'cn',
value: 'foo',
});
f.matches({cn: 'abc'}); => true
f.matches({cn: 'foobar'}); => false
```
# AndFilter
The and filter is a complex filter that simply contains "child" filters. The
object will have a `filters` property which is an array of `Filter` objects. The
`name` property will be `and`.
The string syntax for an and filter is (assuming below we're and'ing two
equality filters):
```
(&(cn=foo)(sn=bar))
```
The `matches()` method will return true IFF the passed in object matches all
the filters in the `filters` array.
```js
const f = new AndFilter({
filters: [
new EqualityFilter({
attribute: 'cn',
value: 'foo'
}),
new EqualityFilter({
attribute: 'sn',
value: 'bar'
})
]
});
f.matches({cn: 'foo', sn: 'bar'}); => true
f.matches({cn: 'foo', sn: 'baz'}); => false
```
# OrFilter
The or filter is a complex filter that simply contains "child" filters. The
object will have a `filters` property which is an array of `Filter` objects. The
`name` property will be `or`.
The string syntax for an or filter is (assuming below we're or'ing two
equality filters):
```
(|(cn=foo)(sn=bar))
```
The `matches()` method will return true IFF the passed in object matches *any*
of the filters in the `filters` array.
```js
const f = new OrFilter({
filters: [
new EqualityFilter({
attribute: 'cn',
value: 'foo'
}),
new EqualityFilter({
attribute: 'sn',
value: 'bar'
})
]
});
f.matches({cn: 'foo', sn: 'baz'}); => true
f.matches({cn: 'bar', sn: 'baz'}); => false
```
# NotFilter
The not filter is a complex filter that contains a single "child" filter. The
object will have a `filter` property which is an instance of a `Filter` object.
The `name` property will be `not`.
The string syntax for a not filter is (assuming below we're not'ing an
equality filter):
```
(!(cn=foo))
```
The `matches()` method will return true IFF the passed in object does not match
the filter in the `filter` property.
```js
const f = new NotFilter({
filter: new EqualityFilter({
attribute: 'cn',
value: 'foo'
})
});
f.matches({cn: 'bar'}); => true
f.matches({cn: 'foo'}); => false
```
# ApproximateFilter
The approximate filter is used to check "approximate" matching of
attribute/value assertions. This object will have an `attribute` and
`value` property, and the `name` property will be `approx`.
As a side point, this is a useless filter. It's really only here if you have
some whacky client that's sending this. It just does an exact match (which
is what ActiveDirectory does too).
The string syntax for an equality filter is `(attr~=value)`.
The `matches()` method will return true IFF the passed in object has a
key matching `attribute` and a value exactly matching `value`.
```js
const f = new ApproximateFilter({
attribute: 'cn',
value: 'foo'
});
f.matches({cn: 'foo'}); => true
f.matches({cn: 'bar'}); => false
```

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

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

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

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

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

@ -0,0 +1,614 @@
---
title: Server API | ldapjs
---
# ldapjs Server API
<div class="intro">
This document covers the ldapjs server API and assumes that you are familiar
with LDAP. If you're not, read the [guide](guide.html) first.
</div>
# Create a server
The code to create a new server looks like:
```js
const server = ldap.createServer();
```
The full list of options is:
||log||You can optionally pass in a Bunyan compatible logger instance the client will use to acquire a child logger.||
||certificate||A PEM-encoded X.509 certificate; will cause this server to run in TLS mode.||
||key||A PEM-encoded private key that corresponds to _certificate_ for SSL.||
### Note On Logger
The passed in logger is expected to conform to the Log4j standard API.
Internally, [abstract-logging](https://www.npmjs.com/packages/abstract-logging) is
used to implement the interface. As a result, no log messages will be generated
unless an external logger is supplied.
Known compatible loggers are:
+ [Bunyan](https://www.npmjs.com/package/bunyan)
+ [Pino](https://www.npmjs.com/package/pino)
## Properties on the server object
### maxConnections
Set this property to reject connections when the server's connection count gets
high.
### connections (getter only) - DEPRECATED
The number of concurrent connections on the server. This property is deprecated,
please use server.getConnections() instead.
### url
Returns the fully qualified URL this server is listening on. For example:
`ldaps://10.1.2.3:1636`. If you haven't yet called `listen`, it will always
return `ldap://localhost:389`.
### Event: 'close'
`function() {}`
Emitted when the server closes.
## Listening for requests
The LDAP server API wraps up and mirrors the node.js `server.listen` family of
APIs.
After calling `listen`, the property `url` on the server object itself will be
available.
Example:
```js
server.listen(389, '127.0.0.1', function() {
console.log('LDAP server listening at: ' + server.url);
});
```
### Port and Host
`listen(port, [host], [callback])`
Begin accepting connections on the specified port and host. If the host is
omitted, the server will accept connections directed to the IPv4 address
`127.0.0.1`. To listen on any other address, supply said address as the `host`
parameter. For example, to listen on all available IPv6 addresses supply
`::` as the `host` (note, this _may_ also result in listening on all
available IPv4 addresses, depending on operating system behavior).
We highly recommend being as explicit as possible with the `host` parameter.
Listening on all available addresses (through `::` or `0.0.0.0`) can lead
to potential security issues.
This function is asynchronous. The last parameter callback will be called when
the server has been bound.
### Unix Domain Socket
`listen(path, [callback])`
Start a UNIX socket server listening for connections on the given path.
This function is asynchronous. The last parameter callback will be called when
the server has been bound.
### File descriptor
`listenFD(fd)`
Start a server listening for connections on the given file descriptor.
This file descriptor must have already had the `bind(2)` and `listen(2)` system
calls invoked on it. Additionally, it must be set non-blocking; try
`fcntl(fd, F_SETFL, O_NONBLOCK)`.
## Inspecting server state
### server.getConnections(callback)
The LDAP server API mirrors the [Node.js `server.getConnections` API](https://nodejs.org/dist/latest-v12.x/docs/api/net.html#net_server_getconnections_callback). Callback
should take two arguments err and count.
# Routes
The LDAP server API is meant to be the LDAP-equivalent of the express/restify
paradigm of programming. Essentially every method is of the form
`OP(req, res, next)` where OP is one of bind, add, del, etc. You can chain
handlers together by calling `next()` and ordering your functions in the
definition of the route. For example:
```js
function authorize(req, res, next) {
if (!req.connection.ldap.bindDN.equals('cn=root'))
return next(new ldap.InsufficientAccessRightsError());
return next();
}
server.search('o=example', authorize, function(req, res, next) { ... });
```
Note that ldapjs is also slightly different, since it's often going to be backed
to a DB-like entity, in that it also has an API where you can pass in a
'backend' object. This is necessary if there are persistent connection pools,
caching, etc. that need to be placed in an object.
For example [ldapjs-riak](https://github.com/mcavage/node-ldapjs-riak) is a
complete implementation of the LDAP protocol over
[Riak](https://github.com/basho/riak). Getting an LDAP server up with riak
looks like:
```js
const ldap = require('ldapjs');
const ldapRiak = require('ldapjs-riak');
const server = ldap.createServer();
const backend = ldapRiak.createBackend({
"host": "localhost",
"port": 8098,
"bucket": "example",
"indexes": ["l", "cn"],
"uniqueIndexes": ["uid"],
"numConnections": 5
});
server.add("o=example",
backend,
backend.add());
...
```
The first parameter to an ldapjs route is always the point in the
tree to mount the handler chain at. The second argument is _optionally_ a
backend object. After that you can pass in an arbitrary combination of
functions in the form `f(req, res, next)` or arrays of functions of the same
signature (ldapjs will unroll them).
Unlike HTTP, LDAP operations do not have a heterogeneous wire format, so each
operation requires specific methods/fields on the request/response
objects. However, there is a `.use()` method availabe, similar to
that on express/connect, allowing you to chain up "middleware":
```js
server.use(function(req, res, next) {
console.log('hello world');
return next();
});
```
## Common Request Elements
All request objects have the `dn` getter on it, which is "context-sensitive"
and returns the point in the tree that the operation wants to operate on. The
LDAP protocol itself sadly doesn't define operations this way, and has a unique
name for just about every op. So, ldapjs calls it `dn`. The DN object itself
is documented at [DN](dn.html).
All requests have an optional array of `Control` objects. `Control` will have
the properties `type` (string), `criticality` (boolean), and optionally, a
string `value`.
All request objects will have a `connection` object, which is the `net.Socket`
associated to this request. Off the `connection` object is an `ldap` object.
The most important property to pay attention to is the `bindDN` property
which will be an instance of an `ldap.DN` object. This is what the client
authenticated as on this connection. If the client didn't bind, then a DN object
will be there defaulted to `cn=anonymous`.
Additionally, request will have a `logId` parameter you can use to uniquely
identify the request/connection pair in logs (includes the LDAP messageId).
## Common Response Elements
All response objects will have an `end` method on them. By default, calling
`res.end()` with no arguments will return SUCCESS (0x00) to the client
(with the exception of `compare` which will return COMPARE\_TRUE (0x06)). You
can pass in a status code to the `end()` method to return an alternate status
code.
However, it's more common/easier to use the `return next(new LDAPError())`
pattern, since ldapjs will fill in the extra LDAPResult fields like matchedDN
and error message for you.
## Errors
ldapjs includes an exception hierarchy that directly corresponds to the RFC list
of error codes. The complete list is documented in [errors](errors.html). But
the paradigm is something defined like CONSTRAINT\_VIOLATION in the RFC would be
`ConstraintViolationError` in ldapjs. Upon calling `next(new LDAPError())`,
ldapjs will _stop_ calling your handler chain. For example:
```js
server.search('o=example',
(req, res, next) => { return next(); },
(req, res, next) => { return next(new ldap.OperationsError()); },
(req, res, next) => { res.end(); }
);
```
In the code snipped above, the third handler would never get invoked.
# Bind
Adds a mount in the tree to perform LDAP binds with. Example:
```js
server.bind('ou=people, o=example', (req, res, next) => {
console.log('bind DN: ' + req.dn.toString());
console.log('bind PW: ' + req.credentials);
res.end();
});
```
## BindRequest
BindRequest objects have the following properties:
### version
The LDAP protocol version the client is requesting to run this connection on.
Note that ldapjs only supports LDAP version 3.
### name
The DN the client is attempting to bind as (note this is the same as the `dn`
property).
### authentication
The method of authentication. Right now only `simple` is supported.
### credentials
The credentials to go with the `name/authentication` pair. For `simple`, this
will be the plain-text password.
## BindResponse
No extra methods above an `LDAPResult` API call.
# Add
Adds a mount in the tree to perform LDAP adds with.
```js
server.add('ou=people, o=example', (req, res, next) => {
console.log('DN: ' + req.dn.toString());
console.log('Entry attributes: ' + req.toObject().attributes);
res.end();
});
```
## AddRequest
AddRequest objects have the following properties:
### entry
The DN the client is attempting to add (this is the same as the `dn`
property).
### attributes
The set of attributes in this entry. This will be an array of
`Attribute` objects (which have a type and an array of values). This directly
maps to how the request came in off the wire. It's likely you'll want to use
`toObject()` and iterate that way, since that will transform an AddRequest into
a standard JavaScript object.
### toObject()
This operation will return a plain JavaScript object from the request that looks
like:
```js
{
dn: 'cn=foo, o=example', // string, not DN object
attributes: {
cn: ['foo'],
sn: ['bar'],
objectclass: ['person', 'top']
}
}
```
## AddResponse
No extra methods above an `LDAPResult` API call.
# Search
Adds a handler for the LDAP search operation.
```js
server.search('o=example', (req, res, next) => {
console.log('base object: ' + req.dn.toString());
console.log('scope: ' + req.scope);
console.log('filter: ' + req.filter.toString());
res.end();
});
```
## SearchRequest
SearchRequest objects have the following properties:
### baseObject
The DN the client is attempting to start the search at (equivalent to `dn`).
### scope
(string) one of:
* base
* one
* sub
### derefAliases
An integer (defined in the LDAP protocol). Defaults to '0' (meaning
never deref).
### sizeLimit
The number of entries to return. Defaults to '0' (unlimited). ldapjs doesn't
currently automatically enforce this, but probably will at some point.
### timeLimit
Maximum amount of time the server should take in sending search entries.
Defaults to '0' (unlimited).
### typesOnly
Whether to return only the names of attributes, and not the values. Defaults to
'false'. ldapjs will take care of this for you.
### filter
The [filter](filters.html) object that the client requested. Notably this has
a `matches()` method on it that you can leverage. For an example of
introspecting a filter, take a look at the ldapjs-riak source.
### attributes
An optional list of attributes to restrict the returned result sets to. ldapjs
will automatically handle this for you.
## SearchResponse
### send(entry)
Allows you to send a `SearchEntry` object. You do not need to
explicitly pass in a `SearchEntry` object, and can instead just send a plain
JavaScript object that matches the format used from `AddRequest.toObject()`.
```js
server.search('o=example', (req, res, next) => {
const obj = {
dn: 'o=example',
attributes: {
objectclass: ['top', 'organization'],
o: ['example']
}
};
if (req.filter.matches(obj))
res.send(obj)
res.end();
});
```
# modify
Allows you to handle an LDAP modify operation.
```js
server.modify('o=example', (req, res, next) => {
console.log('DN: ' + req.dn.toString());
console.log('changes:');
for (const c of req.changes) {
console.log(' operation: ' + c.operation);
console.log(' modification: ' + c.modification.toString());
}
res.end();
});
```
## ModifyRequest
ModifyRequest objects have the following properties:
### object
The DN the client is attempting to update (this is the same as the `dn`
property).
### changes
An array of `Change` objects the client is attempting to perform. See below for
details on the `Change` object.
## Change
The `Change` object will have the following properties:
### operation
A string, and will be one of: 'add', 'delete', or 'replace'.
### modification
Will be an `Attribute` object, which will have a 'type' (string) field, and
'vals', which will be an array of string values.
## ModifyResponse
No extra methods above an `LDAPResult` API call.
# del
Allows you to handle an LDAP delete operation.
```js
server.del('o=example', (req, res, next) => {
console.log('DN: ' + req.dn.toString());
res.end();
});
```
## DeleteRequest
### entry
The DN the client is attempting to delete (this is the same as the `dn`
property).
## DeleteResponse
No extra methods above an `LDAPResult` API call.
# compare
Allows you to handle an LDAP compare operation.
```js
server.compare('o=example', (req, res, next) => {
console.log('DN: ' + req.dn.toString());
console.log('attribute name: ' + req.attribute);
console.log('attribute value: ' + req.value);
res.end(req.value === 'foo');
});
```
## CompareRequest
### entry
The DN the client is attempting to compare (this is the same as the `dn`
property).
### attribute
The string name of the attribute to compare values of.
### value
The string value of the attribute to compare.
## CompareResponse
The `end()` method for compare takes a boolean, as opposed to a numeric code
(you can still pass in a numeric LDAP status code if you want). Beyond
that, there are no extra methods above an `LDAPResult` API call.
# modifyDN
Allows you to handle an LDAP modifyDN operation.
```js
server.modifyDN('o=example', (req, res, next) => {
console.log('DN: ' + req.dn.toString());
console.log('new RDN: ' + req.newRdn.toString());
console.log('deleteOldRDN: ' + req.deleteOldRdn);
console.log('new superior: ' +
(req.newSuperior ? req.newSuperior.toString() : ''));
res.end();
});
```
## ModifyDNRequest
### entry
The DN the client is attempting to rename (this is the same as the `dn`
property).
### newRdn
The leaf RDN the client wants to rename this entry to. This will be a DN object.
### deleteOldRdn
Whether or not to delete the old RDN (i.e., rename vs copy). Defaults to 'true'.
### newSuperior
Optional (DN). If the modifyDN operation wishes to relocate the entry in the
tree, the `newSuperior` field will contain the new parent.
## ModifyDNResponse
No extra methods above an `LDAPResult` API call.
# exop
Allows you to handle an LDAP extended operation. Extended operations are pretty
much arbitrary extensions, by definition. Typically the extended 'name' is an
OID, but ldapjs makes no such restrictions; it just needs to be a string.
Unlike the other operations, extended operations don't map to any location in
the tree, so routing here will be exact match, as opposed to subtree.
```js
// LDAP whoami
server.exop('1.3.6.1.4.1.4203.1.11.3', (req, res, next) => {
console.log('name: ' + req.name);
console.log('value: ' + req.value);
res.value = 'u:xxyyz@EXAMPLE.NET';
res.end();
return next();
});
```
## ExtendedRequest
### name
Will always be a match to the route-defined name. Clients must include this
in their requests.
### value
Optional string. The arbitrary blob the client sends for this extended
operation.
## ExtendedResponse
### name
The name of the extended operation. ldapjs will automatically set this.
### value
The arbitrary (string) value to send back as part of the response.
# unbind
ldapjs by default provides an unbind handler that just disconnects the client
and cleans up any internals (in ldapjs core). You can override this handler
if you need to clean up any items in your backend, or perform any other cleanup
tasks you need to.
```js
server.unbind((req, res, next) => {
res.end();
});
```
Note that the LDAP unbind operation actually doesn't send any response (by
definition in the RFC), so the UnbindResponse is really just a stub that
ultimately calls `net.Socket.end()` for you. There are no properties available
on either the request or response objects, except, of course, for `end()` on the
response.

View File

@ -0,0 +1,65 @@
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')
const routeTo = threads.getNext()
threads[routeTo].send({ type: 'connection' }, socket)
})
for (let i = 0; i < os.cpus().length; i++) {
const thread = cluster.fork({
id: i
})
thread.id = i
thread.on('message', function () {
})
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)
const 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) {
console.log('ldapjs search initiated on ' + threadId.toString())
const obj = {
dn: req.dn.toString(),
attributes: {
objectclass: ['organization', 'top'],
o: 'example'
}
}
if (req.filter.matches(obj.attributes)) { res.send(obj) }
res.end()
})
}

65
node_modules/ldapjs/examples/cluster-threading.js generated vendored Normal file
View File

@ -0,0 +1,65 @@
const cluster = require('cluster')
const ldap = require('ldapjs')
const os = require('os')
const threads = []
threads.getNext = function () {
return (Math.floor(Math.random() * this.length))
}
const serverOptions = {
connectionRouter: (socket) => {
socket.pause()
console.log('ldapjs client requesting connection')
const routeTo = threads.getNext()
threads[routeTo].send({ type: 'connection' }, socket)
}
}
const server = ldap.createServer(serverOptions)
if (cluster.isMaster) {
for (let i = 0; i < os.cpus().length; i++) {
const thread = cluster.fork({
id: i
})
thread.id = i
thread.on('message', function () {
})
threads.push(thread)
}
server.listen(1389, function () {
console.log('ldapjs listening at ' + server.url)
})
} else {
const threadId = process.env.id
serverOptions.connectionRouter = () => {
console.log('should not be hit')
}
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) {
console.log('ldapjs search initiated on ' + threadId.toString())
const obj = {
dn: req.dn.toString(),
attributes: {
objectclass: ['organization', 'top'],
o: 'example'
}
}
if (req.filter.matches(obj.attributes)) { res.send(obj) }
res.end()
})
}

177
node_modules/ldapjs/examples/inmemory.js generated vendored Normal file
View File

@ -0,0 +1,177 @@
const ldap = require('../lib/index')
/// --- 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=smartdc'
const db = {}
const server = ldap.createServer()
server.bind('cn=root', function (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, function (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, function (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, function (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)) }
let matches = false
const vals = db[dn][req.attribute]
for (let i = 0; i < vals.length; i++) {
if (vals[i] === req.value) {
matches = true
break
}
}
res.end(matches)
return next()
})
server.del(SUFFIX, authorize, function (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, function (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]
let mod
for (let i = 0; i < req.changes.length; i++) {
mod = req.changes[i].modification
switch (req.changes[i].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 {
mod.vals.forEach(function (v) {
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, function (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,
attributes: db[dn]
})
}
res.end()
return next()
case 'one':
scopeCheck = function (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 = function (k) {
return (req.dn.equals(k) || req.dn.parentOf(k))
}
break
}
Object.keys(db).forEach(function (key) {
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, function () {
console.log('LDAP server up at: %s', server.url)
})

1345
node_modules/ldapjs/lib/client/client.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

7
node_modules/ldapjs/lib/client/constants.js generated vendored Normal file
View File

@ -0,0 +1,7 @@
'use strict'
module.exports = {
// https://tools.ietf.org/html/rfc4511#section-4.1.1
// Message identifiers are an integer between (0, maxint).
MAX_MSGID: Math.pow(2, 31) - 1
}

23
node_modules/ldapjs/lib/client/index.js generated vendored Normal file
View File

@ -0,0 +1,23 @@
'use strict'
const logger = require('../logger')
const Client = require('./client')
module.exports = {
Client,
createClient: function createClient (options) {
if (isObject(options) === false) throw TypeError('options (object) required')
if (options.url && typeof options.url !== 'string' && !Array.isArray(options.url)) throw TypeError('options.url (string|array) required')
if (options.socketPath && typeof options.socketPath !== 'string') throw TypeError('options.socketPath must be a string')
if ((options.url && options.socketPath) || !(options.url || options.socketPath)) throw TypeError('options.url ^ options.socketPath (String) required')
if (!options.log) options.log = logger
if (isObject(options.log) !== true) throw TypeError('options.log must be an object')
if (!options.log.child) options.log.child = function () { return options.log }
return new Client(options)
}
}
function isObject (input) {
return Object.prototype.toString.apply(input) === '[object Object]'
}

View File

@ -0,0 +1,25 @@
'use strict'
const { MAX_MSGID } = require('../constants')
/**
* Compare a reference id with another id to determine "greater than or equal"
* between the two values according to a sliding window.
*
* @param {integer} ref
* @param {integer} comp
*
* @returns {boolean} `true` if the `comp` value is >= to the `ref` value
* within the computed window, otherwise `false`.
*/
module.exports = function geWindow (ref, comp) {
let max = ref + Math.floor(MAX_MSGID / 2)
const min = ref
if (max >= MAX_MSGID) {
// Handle roll-over
max = max - MAX_MSGID - 1
return ((comp <= max) || (comp >= min))
} else {
return ((comp <= max) && (comp >= min))
}
}

View File

@ -0,0 +1,23 @@
'use strict'
const { MAX_MSGID } = require('../constants')
/**
* Returns a function that generates message identifiers. According to RFC 4511
* the identifers should be `(0, MAX_MSGID)`. The returned function handles
* this and wraps around when the maximum has been reached.
*
* @param {integer} [start=0] Starting number in the identifier sequence.
*
* @returns {function} This function accepts no parameters and returns an
* increasing sequence identifier each invocation until it reaches the maximum
* identifier. At this point the sequence starts over.
*/
module.exports = function idGeneratorFactory (start = 0) {
let currentID = start
return function nextID () {
const id = currentID + 1
currentID = (id >= MAX_MSGID) ? 1 : id
return currentID
}
}

161
node_modules/ldapjs/lib/client/message-tracker/index.js generated vendored Normal file
View File

@ -0,0 +1,161 @@
'use strict'
const idGeneratorFactory = require('./id-generator')
const purgeAbandoned = require('./purge-abandoned')
/**
* Returns a message tracker object that keeps track of which message
* identifiers correspond to which message handlers. Also handles keeping track
* of abandoned messages.
*
* @param {object} options
* @param {string} options.id An identifier for the tracker.
* @param {object} options.parser An object that will be used to parse messages.
*
* @returns {MessageTracker}
*/
module.exports = function messageTrackerFactory (options) {
if (Object.prototype.toString.call(options) !== '[object Object]') {
throw Error('options object is required')
}
if (!options.id || typeof options.id !== 'string') {
throw Error('options.id string is required')
}
if (!options.parser || Object.prototype.toString.call(options.parser) !== '[object Object]') {
throw Error('options.parser object is required')
}
let currentID = 0
const nextID = idGeneratorFactory()
const messages = new Map()
const abandoned = new Map()
/**
* @typedef {object} MessageTracker
* @property {string} id The identifier of the tracker as supplied via the options.
* @property {object} parser The parser object given by the the options.
*/
const tracker = {
id: options.id,
parser: options.parser
}
/**
* Count of messages awaiting response.
*
* @alias pending
* @memberof! MessageTracker#
*/
Object.defineProperty(tracker, 'pending', {
get () {
return messages.size
}
})
/**
* Move a specific message to the abanded track.
*
* @param {integer} msgID The identifier for the message to move.
*
* @memberof MessageTracker
* @method abandon
*/
tracker.abandon = function abandonMessage (msgID) {
if (messages.has(msgID) === false) return false
const toAbandon = messages.get(msgID)
abandoned.set(msgID, {
age: currentID,
message: toAbandon.message,
cb: toAbandon.callback
})
return messages.delete(msgID)
}
/**
* @typedef {object} Tracked
* @property {object} message The tracked message. Usually the outgoing
* request object.
* @property {Function} callback The handler to use when receiving a
* response to the tracked message.
*/
/**
* Retrieves the message handler for a message. Removes abandoned messages
* that have been given time to be resolved.
*
* @param {integer} msgID The identifier for the message to get the handler for.
*
* @memberof MessageTracker
* @method fetch
*/
tracker.fetch = function fetchMessage (msgID) {
const tracked = messages.get(msgID)
if (tracked) {
purgeAbandoned(msgID, abandoned)
return tracked
}
// We sent an abandon request but the server either wasn't able to process
// it or has not received it yet. Therefore, we received a response for the
// abandoned message. So we must return the abandoned message's callback
// to be processed normally.
const abandonedMsg = abandoned.get(msgID)
if (abandonedMsg) {
return { message: abandonedMsg, callback: abandonedMsg.cb }
}
return null
}
/**
* Removes all message tracks, cleans up the abandoned track, and invokes
* a callback for each message purged.
*
* @param {function} cb A function with the signature `(msgID, handler)`.
*
* @memberof MessageTracker
* @method purge
*/
tracker.purge = function purgeMessages (cb) {
messages.forEach((val, key) => {
purgeAbandoned(key, abandoned)
tracker.remove(key)
cb(key, val.callback)
})
}
/**
* Removes a message from all tracking.
*
* @param {integer} msgID The identifier for the message to remove from tracking.
*
* @memberof MessageTracker
* @method remove
*/
tracker.remove = function removeMessage (msgID) {
if (messages.delete(msgID) === false) {
abandoned.delete(msgID)
}
}
/**
* Add a message handler to be tracked.
*
* @param {object} message The message object to be tracked. This object will
* have a new property added to it: `messageId`.
* @param {function} callback The handler for the message.
*
* @memberof MessageTracker
* @method track
*/
tracker.track = function trackMessage (message, callback) {
currentID = nextID()
// This side effect is not ideal but the client doesn't attach the tracker
// to itself until after the `.connect` method has fired. If this can be
// refactored later, then we can possibly get rid of this side effect.
message.messageId = currentID
messages.set(currentID, { callback, message })
}
return tracker
}

View File

@ -0,0 +1,34 @@
'use strict'
const { AbandonedError } = require('../../errors')
const geWindow = require('./ge-window')
/**
* Given a `msgID` and a set of `abandoned` messages, remove any abandoned
* messages that existed _prior_ to the specified `msgID`. For example, let's
* assume the server has sent 3 messages:
*
* 1. A search message.
* 2. An abandon message for the search message.
* 3. A new search message.
*
* When the response for message #1 comes in, if it does, it will be processed
* normally due to the specification. Message #2 will not receive a response, or
* if the server does send one since the spec sort of allows it, we won't do
* anything with it because we just discard that listener. Now the response
* for message #3 comes in. At this point, we will issue a purge of responses
* by passing in `msgID = 3`. This result is that we will remove the tracking
* for message #1.
*
* @param {integer} msgID An upper bound for the messages to be purged.
* @param {Map} abandoned A set of abandoned messages. Each message is an object
* `{ age: <id>, cb: <func> }` where `age` was the current message id when the
* abandon message was sent.
*/
module.exports = function purgeAbandoned (msgID, abandoned) {
abandoned.forEach((val, key) => {
if (geWindow(val.age, msgID) === false) return
val.cb(new AbandonedError('client request abandoned'))
abandoned.delete(key)
})
}

View File

@ -0,0 +1,36 @@
'use strict'
/**
* Adds requests to the queue. If a timeout has been added to the queue then
* this will freeze the queue with the newly added item, flush it, and then
* unfreeze it when the queue has been cleared.
*
* @param {object} message An LDAP message object.
* @param {object} expect An expectation object.
* @param {object} emitter An event emitter or `null`.
* @param {function} cb A callback to invoke when the request is finished.
*
* @returns {boolean} `true` if the requested was queued. `false` if the queue
* is not accepting any requests.
*/
module.exports = function enqueue (message, expect, emitter, cb) {
if (this._queue.size >= this.size || this._frozen) {
return false
}
this._queue.add({ message, expect, emitter, cb })
if (this.timeout === 0) return true
if (this._timer === null) return true
// A queue can have a specified time allotted for it to be cleared. If that
// time has been reached, reject new entries until the queue has been cleared.
this._timer = setTimeout(queueTimeout.bind(this), this.timeout)
return true
function queueTimeout () {
this.freeze()
this.purge()
}
}

24
node_modules/ldapjs/lib/client/request-queue/flush.js generated vendored Normal file
View File

@ -0,0 +1,24 @@
'use strict'
/**
* Invokes all requests in the queue by passing them to the supplied callback
* function and then clears all items from the queue.
*
* @param {function} cb A function used to handle the requests.
*/
module.exports = function flush (cb) {
if (this._timer) {
clearTimeout(this._timer)
this._timer = null
}
// We must get a local copy of the queue and clear it before iterating it.
// The client will invoke this flush function _many_ times. If we try to
// iterate it without a local copy and clearing first then we will overflow
// the stack.
const requests = Array.from(this._queue.values())
this._queue.clear()
for (const req of requests) {
cb(req.message, req.expect, req.emitter, req.cb)
}
}

39
node_modules/ldapjs/lib/client/request-queue/index.js generated vendored Normal file
View File

@ -0,0 +1,39 @@
'use strict'
const enqueue = require('./enqueue')
const flush = require('./flush')
const purge = require('./purge')
/**
* Builds a request queue object and returns it.
*
* @param {object} [options]
* @param {integer} [options.size] Maximum size of the request queue. Must be
* a number greater than `0` if supplied. Default: `Infinity`.
* @param {integer} [options.timeout] Time in milliseconds a queue has to
* complete the requests it contains.
*
* @returns {object} A queue instance.
*/
module.exports = function requestQueueFactory (options) {
const opts = Object.assign({}, options)
const q = {
size: (opts.size > 0) ? opts.size : Infinity,
timeout: (opts.timeout > 0) ? opts.timeout : 0,
_queue: new Set(),
_timer: null,
_frozen: false
}
q.enqueue = enqueue.bind(q)
q.flush = flush.bind(q)
q.purge = purge.bind(q)
q.freeze = function freeze () {
this._frozen = true
}
q.thaw = function thaw () {
this._frozen = false
}
return q
}

12
node_modules/ldapjs/lib/client/request-queue/purge.js generated vendored Normal file
View File

@ -0,0 +1,12 @@
'use strict'
const { TimeoutError } = require('../../errors')
/**
* Flushes the queue by rejecting all pending requests with a timeout error.
*/
module.exports = function purge () {
this.flush(function flushCB (a, b, c, cb) {
cb(new TimeoutError('request queue timeout'))
})
}

167
node_modules/ldapjs/lib/client/search_pager.js generated vendored Normal file
View File

@ -0,0 +1,167 @@
'use strict'
const EventEmitter = require('events').EventEmitter
const util = require('util')
const assert = require('assert-plus')
const { PagedResultsControl } = require('@ldapjs/controls')
const CorkedEmitter = require('../corked_emitter.js')
/// --- API
/**
* Handler object for paged search operations.
*
* Provided to consumers in place of the normal search EventEmitter it adds the
* following new events:
* 1. page - Emitted whenever the end of a result page is encountered.
* If this is the last page, 'end' will also be emitted.
* The event passes two arguments:
* 1. The result object (similar to 'end')
* 2. A callback function optionally used to continue the search
* operation if the pagePause option was specified during
* initialization.
* 2. pageError - Emitted if the server does not support paged search results
* If there are no listeners for this event, the 'error' event
* will be emitted (and 'end' will not be). By listening to
* 'pageError', a successful search that lacks paging will be
* able to emit 'end'.
*/
function SearchPager (opts) {
assert.object(opts)
assert.func(opts.callback)
assert.number(opts.pageSize)
assert.func(opts.sendRequest)
CorkedEmitter.call(this, {})
this.callback = opts.callback
this.controls = opts.controls
this.pageSize = opts.pageSize
this.pagePause = opts.pagePause
this.sendRequest = opts.sendRequest
this.controls.forEach(function (control) {
if (control.type === PagedResultsControl.OID) {
// The point of using SearchPager is not having to do this.
// Toss an error if the pagedResultsControl is present
throw new Error('redundant pagedResultControl')
}
})
this.finished = false
this.started = false
const emitter = new EventEmitter()
emitter.on('searchRequest', this.emit.bind(this, 'searchRequest'))
emitter.on('searchEntry', this.emit.bind(this, 'searchEntry'))
emitter.on('end', this._onEnd.bind(this))
emitter.on('error', this._onError.bind(this))
this.childEmitter = emitter
}
util.inherits(SearchPager, CorkedEmitter)
module.exports = SearchPager
/**
* Start the paged search.
*/
SearchPager.prototype.begin = function begin () {
// Starting first page
this._nextPage(null)
}
SearchPager.prototype._onEnd = function _onEnd (res) {
const self = this
let cookie = null
res.controls.forEach(function (control) {
if (control.type === PagedResultsControl.OID) {
cookie = control.value.cookie
}
})
// Pass a noop callback by default for page events
const nullCb = function () { }
if (cookie === null) {
// paged search not supported
this.finished = true
this.emit('page', res, nullCb)
const err = new Error('missing paged control')
err.name = 'PagedError'
if (this.listeners('pageError').length > 0) {
this.emit('pageError', err)
// If the consumer as subscribed to pageError, SearchPager is absolved
// from delivering the fault via the 'error' event. Emitting an 'end'
// event after 'error' breaks the contract that the standard client
// provides, so it's only a possibility if 'pageError' is used instead.
this.emit('end', res)
} else {
this.emit('error', err)
// No end event possible per explanation above.
}
return
}
if (cookie.length === 0) {
// end of paged results
this.finished = true
this.emit('page', nullCb)
this.emit('end', res)
} else {
if (this.pagePause) {
// Wait to fetch next page until callback is invoked
// Halt page fetching if called with error
this.emit('page', res, function (err) {
if (!err) {
self._nextPage(cookie)
} else {
// the paged search has been canceled so emit an end
self.emit('end', res)
}
})
} else {
this.emit('page', res, nullCb)
this._nextPage(cookie)
}
}
}
SearchPager.prototype._onError = function _onError (err) {
this.finished = true
this.emit('error', err)
}
/**
* Initiate a search for the next page using the returned cookie value.
*/
SearchPager.prototype._nextPage = function _nextPage (cookie) {
const controls = this.controls.slice(0)
controls.push(new PagedResultsControl({
value: {
size: this.pageSize,
cookie
}
}))
this.sendRequest(controls, this.childEmitter, this._sendCallback.bind(this))
}
/**
* Callback provided to the client API for successful transmission.
*/
SearchPager.prototype._sendCallback = function _sendCallback (err) {
if (err) {
this.finished = true
if (!this.started) {
// EmitSend error during the first page, bail via callback
this.callback(err, null)
} else {
this.emit('error', err)
}
} else {
// search successfully send
if (!this.started) {
this.started = true
// send self as emitter as the client would
this.callback(null, this)
}
}
}

4
node_modules/ldapjs/lib/controls/index.js generated vendored Normal file
View File

@ -0,0 +1,4 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const controls = require('@ldapjs/controls')
module.exports = controls

50
node_modules/ldapjs/lib/corked_emitter.js generated vendored Normal file
View File

@ -0,0 +1,50 @@
'use strict'
const EventEmitter = require('events').EventEmitter
/**
* A CorkedEmitter is a variant of an EventEmitter where events emitted
* wait for the appearance of the first listener of any kind. That is,
* a CorkedEmitter will store all .emit()s it receives, to be replayed
* later when an .on() is applied.
* It is meant for situations where the consumers of the emitter are
* unable to register listeners right away, and cannot afford to miss
* any events emitted from the start.
* Note that, whenever the first emitter (for any event) appears,
* the emitter becomes uncorked and works as usual for ALL events, and
* will not cache anything anymore. This is necessary to avoid
* re-ordering emits - either everything is being buffered, or nothing.
*/
function CorkedEmitter () {
const self = this
EventEmitter.call(self)
/**
* An array of arguments objects (array-likes) to emit on open.
*/
self._outstandingEmits = []
/**
* Whether the normal flow of emits is restored yet.
*/
self._opened = false
// When the first listener appears, we enqueue an opening.
// It is not done immediately, so that other listeners can be
// registered in the same critical section.
self.once('newListener', function () {
setImmediate(function releaseStoredEvents () {
self._opened = true
self._outstandingEmits.forEach(function (args) {
self.emit.apply(self, args)
})
})
})
}
CorkedEmitter.prototype = Object.create(EventEmitter.prototype)
CorkedEmitter.prototype.emit = function emit (eventName) {
if (this._opened || eventName === 'newListener') {
EventEmitter.prototype.emit.apply(this, arguments)
} else {
this._outstandingEmits.push(arguments)
}
}
module.exports = CorkedEmitter

47
node_modules/ldapjs/lib/errors/codes.js generated vendored Normal file
View File

@ -0,0 +1,47 @@
'use strict'
module.exports = {
LDAP_SUCCESS: 0,
LDAP_OPERATIONS_ERROR: 1,
LDAP_PROTOCOL_ERROR: 2,
LDAP_TIME_LIMIT_EXCEEDED: 3,
LDAP_SIZE_LIMIT_EXCEEDED: 4,
LDAP_COMPARE_FALSE: 5,
LDAP_COMPARE_TRUE: 6,
LDAP_AUTH_METHOD_NOT_SUPPORTED: 7,
LDAP_STRONG_AUTH_REQUIRED: 8,
LDAP_REFERRAL: 10,
LDAP_ADMIN_LIMIT_EXCEEDED: 11,
LDAP_UNAVAILABLE_CRITICAL_EXTENSION: 12,
LDAP_CONFIDENTIALITY_REQUIRED: 13,
LDAP_SASL_BIND_IN_PROGRESS: 14,
LDAP_NO_SUCH_ATTRIBUTE: 16,
LDAP_UNDEFINED_ATTRIBUTE_TYPE: 17,
LDAP_INAPPROPRIATE_MATCHING: 18,
LDAP_CONSTRAINT_VIOLATION: 19,
LDAP_ATTRIBUTE_OR_VALUE_EXISTS: 20,
LDAP_INVALID_ATTRIBUTE_SYNTAX: 21,
LDAP_NO_SUCH_OBJECT: 32,
LDAP_ALIAS_PROBLEM: 33,
LDAP_INVALID_DN_SYNTAX: 34,
LDAP_ALIAS_DEREF_PROBLEM: 36,
LDAP_INAPPROPRIATE_AUTHENTICATION: 48,
LDAP_INVALID_CREDENTIALS: 49,
LDAP_INSUFFICIENT_ACCESS_RIGHTS: 50,
LDAP_BUSY: 51,
LDAP_UNAVAILABLE: 52,
LDAP_UNWILLING_TO_PERFORM: 53,
LDAP_LOOP_DETECT: 54,
LDAP_SORT_CONTROL_MISSING: 60,
LDAP_INDEX_RANGE_ERROR: 61,
LDAP_NAMING_VIOLATION: 64,
LDAP_OBJECTCLASS_VIOLATION: 65,
LDAP_NOT_ALLOWED_ON_NON_LEAF: 66,
LDAP_NOT_ALLOWED_ON_RDN: 67,
LDAP_ENTRY_ALREADY_EXISTS: 68,
LDAP_OBJECTCLASS_MODS_PROHIBITED: 69,
LDAP_AFFECTS_MULTIPLE_DSAS: 71,
LDAP_CONTROL_ERROR: 76,
LDAP_OTHER: 80,
LDAP_PROXIED_AUTHORIZATION_DENIED: 123
}

147
node_modules/ldapjs/lib/errors/index.js generated vendored Normal file
View File

@ -0,0 +1,147 @@
'use strict'
const util = require('util')
const assert = require('assert-plus')
const LDAPResult = require('../messages').LDAPResult
/// --- Globals
const CODES = require('./codes')
const ERRORS = []
/// --- Error Base class
function LDAPError (message, dn, caller) {
if (Error.captureStackTrace) { Error.captureStackTrace(this, caller || LDAPError) }
this.lde_message = message
this.lde_dn = dn
}
util.inherits(LDAPError, Error)
Object.defineProperties(LDAPError.prototype, {
name: {
get: function getName () { return 'LDAPError' },
configurable: false
},
code: {
get: function getCode () { return CODES.LDAP_OTHER },
configurable: false
},
message: {
get: function getMessage () {
return this.lde_message || this.name
},
set: function setMessage (message) {
this.lde_message = message
},
configurable: false
},
dn: {
get: function getDN () {
return (this.lde_dn ? this.lde_dn.toString() : '')
},
configurable: false
}
})
/// --- Exported API
module.exports = {}
module.exports.LDAPError = LDAPError
// Some whacky games here to make sure all the codes are exported
Object.keys(CODES).forEach(function (code) {
module.exports[code] = CODES[code]
if (code === 'LDAP_SUCCESS') { return }
let err = ''
let msg = ''
const pieces = code.split('_').slice(1)
for (let i = 0; i < pieces.length; i++) {
const lc = pieces[i].toLowerCase()
const key = lc.charAt(0).toUpperCase() + lc.slice(1)
err += key
msg += key + ((i + 1) < pieces.length ? ' ' : '')
}
if (!/\w+Error$/.test(err)) { err += 'Error' }
// At this point LDAP_OPERATIONS_ERROR is now OperationsError in $err
// and 'Operations Error' in $msg
module.exports[err] = function (message, dn, caller) {
LDAPError.call(this, message, dn, caller || module.exports[err])
}
module.exports[err].constructor = module.exports[err]
util.inherits(module.exports[err], LDAPError)
Object.defineProperties(module.exports[err].prototype, {
name: {
get: function getName () { return err },
configurable: false
},
code: {
get: function getCode () { return CODES[code] },
configurable: false
}
})
ERRORS[CODES[code]] = {
err,
message: msg
}
})
module.exports.getError = function (res) {
assert.ok(res instanceof LDAPResult, 'res (LDAPResult) required')
const errObj = ERRORS[res.status]
const E = module.exports[errObj.err]
return new E(res.errorMessage || errObj.message,
res.matchedDN || null,
module.exports.getError)
}
module.exports.getMessage = function (code) {
assert.number(code, 'code (number) required')
const errObj = ERRORS[code]
return (errObj && errObj.message ? errObj.message : '')
}
/// --- Custom application errors
function ConnectionError (message) {
LDAPError.call(this, message, null, ConnectionError)
}
util.inherits(ConnectionError, LDAPError)
module.exports.ConnectionError = ConnectionError
Object.defineProperties(ConnectionError.prototype, {
name: {
get: function () { return 'ConnectionError' },
configurable: false
}
})
function AbandonedError (message) {
LDAPError.call(this, message, null, AbandonedError)
}
util.inherits(AbandonedError, LDAPError)
module.exports.AbandonedError = AbandonedError
Object.defineProperties(AbandonedError.prototype, {
name: {
get: function () { return 'AbandonedError' },
configurable: false
}
})
function TimeoutError (message) {
LDAPError.call(this, message, null, TimeoutError)
}
util.inherits(TimeoutError, LDAPError)
module.exports.TimeoutError = TimeoutError
Object.defineProperties(TimeoutError.prototype, {
name: {
get: function () { return 'TimeoutError' },
configurable: false
}
})

84
node_modules/ldapjs/lib/index.js generated vendored Normal file
View File

@ -0,0 +1,84 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const logger = require('./logger')
const client = require('./client')
const Attribute = require('@ldapjs/attribute')
const Change = require('@ldapjs/change')
const Protocol = require('@ldapjs/protocol')
const Server = require('./server')
const controls = require('./controls')
const persistentSearch = require('./persistent_search')
const dn = require('@ldapjs/dn')
const errors = require('./errors')
const filters = require('@ldapjs/filter')
const messages = require('./messages')
const url = require('./url')
const hasOwnProperty = (target, val) => Object.prototype.hasOwnProperty.call(target, val)
/// --- API
module.exports = {
Client: client.Client,
createClient: client.createClient,
Server,
createServer: function (options) {
if (options === undefined) { options = {} }
if (typeof (options) !== 'object') { throw new TypeError('options (object) required') }
if (!options.log) {
options.log = logger
}
return new Server(options)
},
Attribute,
Change,
dn,
DN: dn.DN,
RDN: dn.RDN,
parseDN: dn.DN.fromString,
persistentSearch,
PersistentSearchCache: persistentSearch.PersistentSearchCache,
filters,
parseFilter: filters.parseString,
url,
parseURL: url.parse
}
/// --- Export all the childrenz
let k
for (k in Protocol) {
if (hasOwnProperty(Protocol, k)) { module.exports[k] = Protocol[k] }
}
for (k in messages) {
if (hasOwnProperty(messages, k)) { module.exports[k] = messages[k] }
}
for (k in controls) {
if (hasOwnProperty(controls, k)) { module.exports[k] = controls[k] }
}
for (k in filters) {
if (hasOwnProperty(filters, k)) {
if (k !== 'parse' && k !== 'parseString') { module.exports[k] = filters[k] }
}
}
for (k in errors) {
if (hasOwnProperty(errors, k)) {
module.exports[k] = errors[k]
}
}

6
node_modules/ldapjs/lib/logger.js generated vendored Normal file
View File

@ -0,0 +1,6 @@
'use strict'
const logger = require('abstract-logging')
logger.child = function () { return logger }
module.exports = logger

39
node_modules/ldapjs/lib/messages/index.js generated vendored Normal file
View File

@ -0,0 +1,39 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const messages = require('@ldapjs/messages')
const Parser = require('./parser')
const SearchResponse = require('./search_response')
/// --- API
module.exports = {
LDAPMessage: messages.LdapMessage,
LDAPResult: messages.LdapResult,
Parser,
AbandonRequest: messages.AbandonRequest,
AbandonResponse: messages.AbandonResponse,
AddRequest: messages.AddRequest,
AddResponse: messages.AddResponse,
BindRequest: messages.BindRequest,
BindResponse: messages.BindResponse,
CompareRequest: messages.CompareRequest,
CompareResponse: messages.CompareResponse,
DeleteRequest: messages.DeleteRequest,
DeleteResponse: messages.DeleteResponse,
ExtendedRequest: messages.ExtensionRequest,
ExtendedResponse: messages.ExtensionResponse,
ModifyRequest: messages.ModifyRequest,
ModifyResponse: messages.ModifyResponse,
ModifyDNRequest: messages.ModifyDnRequest,
ModifyDNResponse: messages.ModifyDnResponse,
SearchRequest: messages.SearchRequest,
SearchEntry: messages.SearchResultEntry,
SearchReference: messages.SearchResultReference,
SearchResponse,
UnbindRequest: messages.UnbindRequest
}

249
node_modules/ldapjs/lib/messages/parser.js generated vendored Normal file
View File

@ -0,0 +1,249 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const EventEmitter = require('events').EventEmitter
const util = require('util')
const assert = require('assert-plus')
const asn1 = require('@ldapjs/asn1')
const logger = require('../logger')
const messages = require('@ldapjs/messages')
const AbandonRequest = messages.AbandonRequest
const AddRequest = messages.AddRequest
const AddResponse = messages.AddResponse
const BindRequest = messages.BindRequest
const BindResponse = messages.BindResponse
const CompareRequest = messages.CompareRequest
const CompareResponse = messages.CompareResponse
const DeleteRequest = messages.DeleteRequest
const DeleteResponse = messages.DeleteResponse
const ExtendedRequest = messages.ExtensionRequest
const ExtendedResponse = messages.ExtensionResponse
const ModifyRequest = messages.ModifyRequest
const ModifyResponse = messages.ModifyResponse
const ModifyDNRequest = messages.ModifyDnRequest
const ModifyDNResponse = messages.ModifyDnResponse
const SearchRequest = messages.SearchRequest
const SearchEntry = messages.SearchResultEntry
const SearchReference = messages.SearchResultReference
const SearchResponse = require('./search_response')
const UnbindRequest = messages.UnbindRequest
const LDAPResult = messages.LdapResult
const Protocol = require('@ldapjs/protocol')
/// --- Globals
const BerReader = asn1.BerReader
/// --- API
function Parser (options = {}) {
assert.object(options)
EventEmitter.call(this)
this.buffer = null
this.log = options.log || logger
}
util.inherits(Parser, EventEmitter)
/**
* The LDAP server/client implementations will receive data from a stream and feed
* it into this method. This method will collect that data into an internal
* growing buffer. As that buffer fills with enough data to constitute a valid
* LDAP message, the data will be parsed, emitted as a message object, and
* reset the buffer to account for any next message in the stream.
*/
Parser.prototype.write = function (data) {
if (!data || !Buffer.isBuffer(data)) { throw new TypeError('data (buffer) required') }
let nextMessage = null
const self = this
function end () {
if (nextMessage) { return self.write(nextMessage) }
return true
}
self.buffer = self.buffer ? Buffer.concat([self.buffer, data]) : data
let ber = new BerReader(self.buffer)
let foundSeq = false
try {
foundSeq = ber.readSequence()
} catch (e) {
this.emit('error', e)
}
if (!foundSeq || ber.remain < ber.length) {
// ENOTENOUGH
return false
} else if (ber.remain > ber.length) {
// ETOOMUCH
// This is an odd branch. Basically, it is setting `nextMessage` to
// a buffer that represents data part of a message subsequent to the one
// being processed. It then re-creates `ber` as a representation of
// the message being processed and advances its offset to the value
// position of the TLV.
// Set `nextMessage` to the bytes subsequent to the current message's
// value bytes. That is, slice from the byte immediately following the
// current message's value bytes until the end of the buffer.
nextMessage = self.buffer.slice(ber.offset + ber.length)
const currOffset = ber.offset
ber = new BerReader(ber.buffer.subarray(0, currOffset + ber.length))
ber.readSequence()
assert.equal(ber.remain, ber.length)
}
// If we're here, ber holds the message, and nextMessage is temporarily
// pointing at the next sequence of data (if it exists)
self.buffer = null
let message
try {
if (Object.prototype.toString.call(ber) === '[object BerReader]') {
// Parse the BER into a JavaScript object representation. The message
// objects require the full sequence in order to construct the object.
// At this point, we have already read the sequence tag and length, so
// we need to rewind the buffer a bit. The `.sequenceToReader` method
// does this for us.
message = messages.LdapMessage.parse(ber.sequenceToReader())
} else {
// Bail here if peer isn't speaking protocol at all
message = this.getMessage(ber)
}
if (!message) {
return end()
}
// TODO: find a better way to handle logging now that messages and the
// server are decoupled. ~ jsumners 2023-02-17
message.log = this.log
} catch (e) {
this.emit('error', e, message)
return false
}
this.emit('message', message)
return end()
}
Parser.prototype.getMessage = function (ber) {
assert.ok(ber)
const self = this
const messageId = ber.readInt()
const type = ber.readSequence()
let Message
switch (type) {
case Protocol.operations.LDAP_REQ_ABANDON:
Message = AbandonRequest
break
case Protocol.operations.LDAP_REQ_ADD:
Message = AddRequest
break
case Protocol.operations.LDAP_RES_ADD:
Message = AddResponse
break
case Protocol.operations.LDAP_REQ_BIND:
Message = BindRequest
break
case Protocol.operations.LDAP_RES_BIND:
Message = BindResponse
break
case Protocol.operations.LDAP_REQ_COMPARE:
Message = CompareRequest
break
case Protocol.operations.LDAP_RES_COMPARE:
Message = CompareResponse
break
case Protocol.operations.LDAP_REQ_DELETE:
Message = DeleteRequest
break
case Protocol.operations.LDAP_RES_DELETE:
Message = DeleteResponse
break
case Protocol.operations.LDAP_REQ_EXTENSION:
Message = ExtendedRequest
break
case Protocol.operations.LDAP_RES_EXTENSION:
Message = ExtendedResponse
break
case Protocol.operations.LDAP_REQ_MODIFY:
Message = ModifyRequest
break
case Protocol.operations.LDAP_RES_MODIFY:
Message = ModifyResponse
break
case Protocol.operations.LDAP_REQ_MODRDN:
Message = ModifyDNRequest
break
case Protocol.operations.LDAP_RES_MODRDN:
Message = ModifyDNResponse
break
case Protocol.operations.LDAP_REQ_SEARCH:
Message = SearchRequest
break
case Protocol.operations.LDAP_RES_SEARCH_ENTRY:
Message = SearchEntry
break
case Protocol.operations.LDAP_RES_SEARCH_REF:
Message = SearchReference
break
case Protocol.operations.LDAP_RES_SEARCH:
Message = SearchResponse
break
case Protocol.operations.LDAP_REQ_UNBIND:
Message = UnbindRequest
break
default:
this.emit('error',
new Error('Op 0x' + (type ? type.toString(16) : '??') +
' not supported'),
new LDAPResult({
messageId,
protocolOp: type || Protocol.operations.LDAP_RES_EXTENSION
}))
return false
}
return new Message({
messageId,
log: self.log
})
}
/// --- Exports
module.exports = Parser

122
node_modules/ldapjs/lib/messages/search_response.js generated vendored Normal file
View File

@ -0,0 +1,122 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const Attribute = require('@ldapjs/attribute')
const {
SearchResultEntry: SearchEntry,
SearchResultReference: SearchReference,
SearchResultDone
} = require('@ldapjs/messages')
const parseDN = require('@ldapjs/dn').DN.fromString
/// --- API
class SearchResponse extends SearchResultDone {
attributes
notAttributes
sentEntries
constructor (options = {}) {
super(options)
this.attributes = options.attributes ? options.attributes.slice() : []
this.notAttributes = []
this.sentEntries = 0
}
}
/**
* Allows you to send a SearchEntry back to the client.
*
* @param {Object} entry an instance of SearchEntry.
* @param {Boolean} nofiltering skip filtering notAttributes and '_' attributes.
* Defaults to 'false'.
*/
SearchResponse.prototype.send = function (entry, nofiltering) {
if (!entry || typeof (entry) !== 'object') { throw new TypeError('entry (SearchEntry) required') }
if (nofiltering === undefined) { nofiltering = false }
if (typeof (nofiltering) !== 'boolean') { throw new TypeError('noFiltering must be a boolean') }
const self = this
const savedAttrs = {}
let save = null
if (entry instanceof SearchEntry || entry instanceof SearchReference) {
if (!entry.messageId) { entry.messageId = this.messageId }
if (entry.messageId !== this.messageId) {
throw new Error('SearchEntry messageId mismatch')
}
} else {
if (!entry.attributes) { throw new Error('entry.attributes required') }
const all = (self.attributes.indexOf('*') !== -1)
// Filter attributes in a plain object according to the magic `_` prefix
// and presence in `notAttributes`.
Object.keys(entry.attributes).forEach(function (a) {
const _a = a.toLowerCase()
if (!nofiltering && _a.length && _a[0] === '_') {
savedAttrs[a] = entry.attributes[a]
delete entry.attributes[a]
} else if (!nofiltering && self.notAttributes.indexOf(_a) !== -1) {
savedAttrs[a] = entry.attributes[a]
delete entry.attributes[a]
} else if (all) {
// do nothing
} else if (self.attributes.length && self.attributes.indexOf(_a) === -1) {
savedAttrs[a] = entry.attributes[a]
delete entry.attributes[a]
}
})
save = entry
entry = new SearchEntry({
objectName: typeof (save.dn) === 'string' ? parseDN(save.dn) : save.dn,
messageId: self.messageId,
attributes: Attribute.fromObject(entry.attributes)
})
}
try {
this.log.debug('%s: sending: %j', this.connection.ldap.id, entry.pojo)
this.connection.write(entry.toBer().buffer)
this.sentEntries++
// Restore attributes
Object.keys(savedAttrs).forEach(function (k) {
save.attributes[k] = savedAttrs[k]
})
} catch (e) {
this.log.warn(e, '%s failure to write message %j',
this.connection.ldap.id, this.pojo)
}
}
SearchResponse.prototype.createSearchEntry = function (object) {
assert.object(object)
const entry = new SearchEntry({
messageId: this.messageId,
objectName: object.objectName || object.dn,
attributes: object.attributes ?? []
})
return entry
}
SearchResponse.prototype.createSearchReference = function (uris) {
if (!uris) { throw new TypeError('uris ([string]) required') }
if (!Array.isArray(uris)) { uris = [uris] }
const self = this
return new SearchReference({
messageId: self.messageId,
uri: uris
})
}
/// --- Exports
module.exports = SearchResponse

109
node_modules/ldapjs/lib/persistent_search.js generated vendored Normal file
View File

@ -0,0 +1,109 @@
/// --- Globals
// var parseDN = require('./dn').parse
const EntryChangeNotificationControl =
require('./controls').EntryChangeNotificationControl
/// --- API
// Cache used to store connected persistent search clients
function PersistentSearch () {
this.clientList = []
}
PersistentSearch.prototype.addClient = function (req, res, callback) {
if (typeof (req) !== 'object') { throw new TypeError('req must be an object') }
if (typeof (res) !== 'object') { throw new TypeError('res must be an object') }
if (callback && typeof (callback) !== 'function') { throw new TypeError('callback must be a function') }
const log = req.log
const client = {}
client.req = req
client.res = res
log.debug('%s storing client', req.logId)
this.clientList.push(client)
log.debug('%s stored client', req.logId)
log.debug('%s total number of clients %s',
req.logId, this.clientList.length)
if (callback) { callback(client) }
}
PersistentSearch.prototype.removeClient = function (req, res, callback) {
if (typeof (req) !== 'object') { throw new TypeError('req must be an object') }
if (typeof (res) !== 'object') { throw new TypeError('res must be an object') }
if (callback && typeof (callback) !== 'function') { throw new TypeError('callback must be a function') }
const log = req.log
log.debug('%s removing client', req.logId)
const client = {}
client.req = req
client.res = res
// remove the client if it exists
this.clientList.forEach(function (element, index, array) {
if (element.req === client.req) {
log.debug('%s removing client from list', req.logId)
array.splice(index, 1)
}
})
log.debug('%s number of persistent search clients %s',
req.logId, this.clientList.length)
if (callback) { callback(client) }
}
function getOperationType (requestType) {
switch (requestType) {
case 'AddRequest':
case 'add':
return 1
case 'DeleteRequest':
case 'delete':
return 2
case 'ModifyRequest':
case 'modify':
return 4
case 'ModifyDNRequest':
case 'modrdn':
return 8
default:
throw new TypeError('requestType %s, is an invalid request type',
requestType)
}
}
function getEntryChangeNotificationControl (req, obj) {
// if we want to return a ECNC
if (req.persistentSearch.value.returnECs) {
const attrs = obj.attributes
const value = {}
value.changeType = getOperationType(attrs.changetype)
// if it's a modDN request, fill in the previous DN
if (value.changeType === 8 && attrs.previousDN) {
value.previousDN = attrs.previousDN
}
value.changeNumber = attrs.changenumber
return new EntryChangeNotificationControl({ value })
} else {
return false
}
}
function checkChangeType (req, requestType) {
return (req.persistentSearch.value.changeTypes &
getOperationType(requestType))
}
/// --- Exports
module.exports = {
PersistentSearchCache: PersistentSearch,
checkChangeType,
getEntryChangeNotificationControl
}

917
node_modules/ldapjs/lib/server.js generated vendored Normal file
View File

@ -0,0 +1,917 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert')
const EventEmitter = require('events').EventEmitter
const net = require('net')
const tls = require('tls')
const util = require('util')
// var asn1 = require('@ldapjs/asn1')
const VError = require('verror').VError
const { DN, RDN } = require('@ldapjs/dn')
const errors = require('./errors')
const Protocol = require('@ldapjs/protocol')
const messages = require('@ldapjs/messages')
const Parser = require('./messages').Parser
const LdapResult = messages.LdapResult
const AbandonResponse = messages.AbandonResponse
const AddResponse = messages.AddResponse
const BindResponse = messages.BindResponse
const CompareResponse = messages.CompareResponse
const DeleteResponse = messages.DeleteResponse
const ExtendedResponse = messages.ExtensionResponse
const ModifyResponse = messages.ModifyResponse
const ModifyDnResponse = messages.ModifyDnResponse
const SearchRequest = messages.SearchRequest
const SearchResponse = require('./messages/search_response')
/// --- Globals
// var Ber = asn1.Ber
// var BerReader = asn1.BerReader
// const DN = dn.DN
// var sprintf = util.format
/// --- Helpers
function mergeFunctionArgs (argv, start, end) {
assert.ok(argv)
if (!start) { start = 0 }
if (!end) { end = argv.length }
const handlers = []
for (let i = start; i < end; i++) {
if (Array.isArray(argv[i])) {
const arr = argv[i]
for (let j = 0; j < arr.length; j++) {
if (typeof arr[j] !== 'function') {
throw new TypeError('Invalid argument type: ' + typeof (arr[j]))
}
handlers.push(arr[j])
}
} else if (typeof argv[i] === 'function') {
handlers.push(argv[i])
} else {
throw new TypeError('Invalid argument type: ' + typeof (argv[i]))
}
}
return handlers
}
function getResponse (req) {
assert.ok(req)
let Response
switch (req.protocolOp) {
case Protocol.operations.LDAP_REQ_BIND:
Response = BindResponse
break
case Protocol.operations.LDAP_REQ_ABANDON:
Response = AbandonResponse
break
case Protocol.operations.LDAP_REQ_ADD:
Response = AddResponse
break
case Protocol.operations.LDAP_REQ_COMPARE:
Response = CompareResponse
break
case Protocol.operations.LDAP_REQ_DELETE:
Response = DeleteResponse
break
case Protocol.operations.LDAP_REQ_EXTENSION:
Response = ExtendedResponse
break
case Protocol.operations.LDAP_REQ_MODIFY:
Response = ModifyResponse
break
case Protocol.operations.LDAP_REQ_MODRDN:
Response = ModifyDnResponse
break
case Protocol.operations.LDAP_REQ_SEARCH:
Response = SearchResponse
break
case Protocol.operations.LDAP_REQ_UNBIND:
// TODO: when the server receives an unbind request this made up response object was returned.
// Instead, we need to just terminate the connection. ~ jsumners
Response = class extends LdapResult {
status = 0
end () {
req.connection.end()
}
}
break
default:
return null
}
assert.ok(Response)
const res = new Response({
messageId: req.messageId,
attributes: ((req instanceof SearchRequest) ? req.attributes : undefined)
})
res.log = req.log
res.connection = req.connection
res.logId = req.logId
if (typeof res.end !== 'function') {
// This is a hack to re-add the original tight coupling of the message
// objects and the server connection.
// TODO: remove this during server refactoring ~ jsumners 2023-02-16
switch (res.protocolOp) {
case 0: {
res.end = abandonResponseEnd
break
}
case Protocol.operations.LDAP_RES_COMPARE: {
res.end = compareResponseEnd
break
}
default: {
res.end = defaultResponseEnd
break
}
}
}
return res
}
/**
* Response connection end handler for most responses.
*
* @param {number} status
*/
function defaultResponseEnd (status) {
if (typeof status === 'number') { this.status = status }
const ber = this.toBer()
this.log.debug('%s: sending: %j', this.connection.ldap.id, this.pojo)
try {
this.connection.write(ber.buffer)
} catch (error) {
this.log.warn(
error,
'%s failure to write message %j',
this.connection.ldap.id,
this.pojo
)
}
}
/**
* Response connection end handler for ABANDON responses.
*/
function abandonResponseEnd () {}
/**
* Response connection end handler for COMPARE responses.
*
* @param {number | boolean} status
*/
function compareResponseEnd (status) {
let result = 0x06
if (typeof status === 'boolean') {
if (status === false) {
result = 0x05
}
} else {
result = status
}
return defaultResponseEnd.call(this, result)
}
function defaultHandler (req, res, next) {
assert.ok(req)
assert.ok(res)
assert.ok(next)
res.matchedDN = req.dn.toString()
res.errorMessage = 'Server method not implemented'
res.end(errors.LDAP_OTHER)
return next()
}
function defaultNoOpHandler (req, res, next) {
assert.ok(req)
assert.ok(res)
assert.ok(next)
res.end()
return next()
}
function noSuffixHandler (req, res, next) {
assert.ok(req)
assert.ok(res)
assert.ok(next)
res.errorMessage = 'No tree found for: ' + req.dn.toString()
res.end(errors.LDAP_NO_SUCH_OBJECT)
return next()
}
function noExOpHandler (req, res, next) {
assert.ok(req)
assert.ok(res)
assert.ok(next)
res.errorMessage = req.requestName + ' not supported'
res.end(errors.LDAP_PROTOCOL_ERROR)
return next()
}
/// --- API
/**
* Constructs a new server that you can call .listen() on, in the various
* forms node supports. You need to first assign some handlers to the various
* LDAP operations however.
*
* The options object currently only takes a certificate/private key, and a
* bunyan logger handle.
*
* This object exposes the following events:
* - 'error'
* - 'close'
*
* @param {Object} options (optional) parameterization object.
* @throws {TypeError} on bad input.
*/
function Server (options) {
if (options) {
if (typeof (options) !== 'object') { throw new TypeError('options (object) required') }
if (typeof (options.log) !== 'object') { throw new TypeError('options.log must be an object') }
if (options.certificate || options.key) {
if (!(options.certificate && options.key) ||
(typeof (options.certificate) !== 'string' &&
!Buffer.isBuffer(options.certificate)) ||
(typeof (options.key) !== 'string' &&
!Buffer.isBuffer(options.key))) {
throw new TypeError('options.certificate and options.key ' +
'(string or buffer) are both required for TLS')
}
}
} else {
options = {}
}
const self = this
EventEmitter.call(this, options)
this._chain = []
this.log = options.log
const log = this.log
function setupConnection (c) {
assert.ok(c)
if (c.type === 'unix') {
c.remoteAddress = self.server.path
c.remotePort = c.fd
} else if (c.socket) {
// TLS
c.remoteAddress = c.socket.remoteAddress
c.remotePort = c.socket.remotePort
}
const rdn = new RDN({ cn: 'anonymous' })
c.ldap = {
id: c.remoteAddress + ':' + c.remotePort,
config: options,
_bindDN: new DN({ rdns: [rdn] })
}
c.addListener('timeout', function () {
log.trace('%s timed out', c.ldap.id)
c.destroy()
})
c.addListener('end', function () {
log.trace('%s shutdown', c.ldap.id)
})
c.addListener('error', function (err) {
log.warn('%s unexpected connection error', c.ldap.id, err)
self.emit('clientError', err)
c.destroy()
})
c.addListener('close', function (closeError) {
log.trace('%s close; had_err=%j', c.ldap.id, closeError)
c.end()
})
c.ldap.__defineGetter__('bindDN', function () {
return c.ldap._bindDN
})
c.ldap.__defineSetter__('bindDN', function (val) {
if (Object.prototype.toString.call(val) !== '[object LdapDn]') {
throw new TypeError('DN required')
}
c.ldap._bindDN = val
return val
})
return c
}
self.newConnection = function (conn) {
// TODO: make `newConnection` available on the `Server` prototype
// https://github.com/ldapjs/node-ldapjs/pull/727/files#r636572294
setupConnection(conn)
log.trace('new connection from %s', conn.ldap.id)
conn.parser = new Parser({
log: options.log
})
conn.parser.on('message', function (req) {
// TODO: this is mutating the `@ldapjs/message` objects.
// We should avoid doing that. ~ jsumners 2023-02-16
req.connection = conn
req.logId = conn.ldap.id + '::' + req.messageId
req.startTime = new Date().getTime()
log.debug('%s: message received: req=%j', conn.ldap.id, req.pojo)
const res = getResponse(req)
if (!res) {
log.warn('Unimplemented server method: %s', req.type)
conn.destroy()
return false
}
// parse string DNs for routing/etc
try {
switch (req.protocolOp) {
case Protocol.operations.LDAP_REQ_BIND: {
req.name = DN.fromString(req.name)
break
}
case Protocol.operations.LDAP_REQ_ADD:
case Protocol.operations.LDAP_REQ_COMPARE:
case Protocol.operations.LDAP_REQ_DELETE: {
if (typeof req.entry === 'string') {
req.entry = DN.fromString(req.entry)
} else if (Object.prototype.toString.call(req.entry) !== '[object LdapDn]') {
throw Error('invalid entry object for operation')
}
break
}
case Protocol.operations.LDAP_REQ_MODIFY: {
req.object = DN.fromString(req.object)
break
}
case Protocol.operations.LDAP_REQ_MODRDN: {
if (typeof req.entry === 'string') {
req.entry = DN.fromString(req.entry)
} else if (Object.prototype.toString.call(req.entry) !== '[object LdapDn]') {
throw Error('invalid entry object for operation')
}
// TODO: handle newRdn/Superior
break
}
case Protocol.operations.LDAP_REQ_SEARCH: {
break
}
default: {
break
}
}
} catch (e) {
return res.end(errors.LDAP_INVALID_DN_SYNTAX)
}
res.connection = conn
res.logId = req.logId
res.requestDN = req.dn
const chain = self._getHandlerChain(req, res)
let i = 0
return (function messageIIFE (err) {
function sendError (sendErr) {
res.status = sendErr.code || errors.LDAP_OPERATIONS_ERROR
res.matchedDN = req.suffix ? req.suffix.toString() : ''
res.errorMessage = sendErr.message || ''
return res.end()
}
function after () {
if (!self._postChain || !self._postChain.length) { return }
function next () {} // stub out next for the post chain
self._postChain.forEach(function (cb) {
cb.call(self, req, res, next)
})
}
if (err) {
log.trace('%s sending error: %s', req.logId, err.stack || err)
self.emit('clientError', err)
sendError(err)
return after()
}
try {
const next = messageIIFE
if (chain.handlers[i]) { return chain.handlers[i++].call(chain.backend, req, res, next) }
if (req.protocolOp === Protocol.operations.LDAP_REQ_BIND && res.status === 0) {
// 0 length == anonymous bind
if (req.dn.length === 0 && req.credentials === '') {
conn.ldap.bindDN = new DN({ rdns: [new RDN({ cn: 'anonymous' })] })
} else {
conn.ldap.bindDN = DN.fromString(req.dn)
}
}
// unbind clear bindDN for safety
// conn should terminate on unbind (RFC4511 4.3)
if (req.protocolOp === Protocol.operations.LDAP_REQ_UNBIND && res.status === 0) {
conn.ldap.bindDN = new DN({ rdns: [new RDN({ cn: 'anonymous' })] })
}
return after()
} catch (e) {
if (!e.stack) { e.stack = e.toString() }
log.error('%s uncaught exception: %s', req.logId, e.stack)
return sendError(new errors.OperationsError(e.message))
}
}())
})
conn.parser.on('error', function (err, message) {
self.emit('error', new VError(err, 'Parser error for %s', conn.ldap.id))
if (!message) { return conn.destroy() }
const res = getResponse(message)
if (!res) { return conn.destroy() }
res.status = 0x02 // protocol error
res.errorMessage = err.toString()
return conn.end(res.toBer())
})
conn.on('data', function (data) {
log.trace('data on %s: %s', conn.ldap.id, util.inspect(data))
conn.parser.write(data)
})
} // end newConnection
this.routes = {}
if ((options.cert || options.certificate) && options.key) {
options.cert = options.cert || options.certificate
this.server = tls.createServer(options, options.connectionRouter ? options.connectionRouter : self.newConnection)
} else {
this.server = net.createServer(options.connectionRouter ? options.connectionRouter : self.newConnection)
}
this.server.log = options.log
this.server.ldap = {
config: options
}
this.server.on('close', function () {
self.emit('close')
})
this.server.on('error', function (err) {
self.emit('error', err)
})
}
util.inherits(Server, EventEmitter)
Object.defineProperties(Server.prototype, {
maxConnections: {
get: function getMaxConnections () {
return this.server.maxConnections
},
set: function setMaxConnections (val) {
this.server.maxConnections = val
},
configurable: false
},
connections: {
get: function getConnections () {
return this.server.connections
},
configurable: false
},
name: {
get: function getName () {
return 'LDAPServer'
},
configurable: false
},
url: {
get: function getURL () {
let str
const addr = this.server.address()
if (!addr) {
return null
}
if (!addr.family) {
str = 'ldapi://'
str += this.host.replace(/\//g, '%2f')
return str
}
if (this.server instanceof tls.Server) {
str = 'ldaps://'
} else {
str = 'ldap://'
}
let host = this.host
// Node 18 switched family from returning a string to returning a number
// https://nodejs.org/api/net.html#serveraddress
if (addr.family === 'IPv6' || addr.family === 6) {
host = '[' + this.host + ']'
}
str += host + ':' + this.port
return str
},
configurable: false
}
})
module.exports = Server
/**
* Adds a handler (chain) for the LDAP add method.
*
* Note that this is of the form f(name, [function]) where the second...N
* arguments can all either be functions or arrays of functions.
*
* @param {String} name the DN to mount this handler chain at.
* @return {Server} this so you can chain calls.
* @throws {TypeError} on bad input
*/
Server.prototype.add = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.operations.LDAP_REQ_ADD, name, args)
}
/**
* Adds a handler (chain) for the LDAP bind method.
*
* Note that this is of the form f(name, [function]) where the second...N
* arguments can all either be functions or arrays of functions.
*
* @param {String} name the DN to mount this handler chain at.
* @return {Server} this so you can chain calls.
* @throws {TypeError} on bad input
*/
Server.prototype.bind = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.operations.LDAP_REQ_BIND, name, args)
}
/**
* Adds a handler (chain) for the LDAP compare method.
*
* Note that this is of the form f(name, [function]) where the second...N
* arguments can all either be functions or arrays of functions.
*
* @param {String} name the DN to mount this handler chain at.
* @return {Server} this so you can chain calls.
* @throws {TypeError} on bad input
*/
Server.prototype.compare = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.operations.LDAP_REQ_COMPARE, name, args)
}
/**
* Adds a handler (chain) for the LDAP delete method.
*
* Note that this is of the form f(name, [function]) where the second...N
* arguments can all either be functions or arrays of functions.
*
* @param {String} name the DN to mount this handler chain at.
* @return {Server} this so you can chain calls.
* @throws {TypeError} on bad input
*/
Server.prototype.del = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.operations.LDAP_REQ_DELETE, name, args)
}
/**
* Adds a handler (chain) for the LDAP exop method.
*
* Note that this is of the form f(name, [function]) where the second...N
* arguments can all either be functions or arrays of functions.
*
* @param {String} name OID to assign this handler chain to.
* @return {Server} this so you can chain calls.
* @throws {TypeError} on bad input.
*/
Server.prototype.exop = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.operations.LDAP_REQ_EXTENSION, name, args, true)
}
/**
* Adds a handler (chain) for the LDAP modify method.
*
* Note that this is of the form f(name, [function]) where the second...N
* arguments can all either be functions or arrays of functions.
*
* @param {String} name the DN to mount this handler chain at.
* @return {Server} this so you can chain calls.
* @throws {TypeError} on bad input
*/
Server.prototype.modify = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.operations.LDAP_REQ_MODIFY, name, args)
}
/**
* Adds a handler (chain) for the LDAP modifyDN method.
*
* Note that this is of the form f(name, [function]) where the second...N
* arguments can all either be functions or arrays of functions.
*
* @param {String} name the DN to mount this handler chain at.
* @return {Server} this so you can chain calls.
* @throws {TypeError} on bad input
*/
Server.prototype.modifyDN = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.operations.LDAP_REQ_MODRDN, name, args)
}
/**
* Adds a handler (chain) for the LDAP search method.
*
* Note that this is of the form f(name, [function]) where the second...N
* arguments can all either be functions or arrays of functions.
*
* @param {String} name the DN to mount this handler chain at.
* @return {Server} this so you can chain calls.
* @throws {TypeError} on bad input
*/
Server.prototype.search = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.operations.LDAP_REQ_SEARCH, name, args)
}
/**
* Adds a handler (chain) for the LDAP unbind method.
*
* This method is different than the others and takes no mount point, as unbind
* is a connection-wide operation, not constrianed to part of the DIT.
*
* @return {Server} this so you can chain calls.
* @throws {TypeError} on bad input
*/
Server.prototype.unbind = function () {
const args = Array.prototype.slice.call(arguments, 0)
return this._mount(Protocol.operations.LDAP_REQ_UNBIND, 'unbind', args, true)
}
Server.prototype.use = function use () {
const args = Array.prototype.slice.call(arguments)
const chain = mergeFunctionArgs(args, 0, args.length)
const self = this
chain.forEach(function (c) {
self._chain.push(c)
})
}
Server.prototype.after = function () {
if (!this._postChain) { this._postChain = [] }
const self = this
mergeFunctionArgs(arguments).forEach(function (h) {
self._postChain.push(h)
})
}
// All these just re-expose the requisite net.Server APIs
Server.prototype.listen = function (port, host, callback) {
if (typeof (port) !== 'number' && typeof (port) !== 'string') { throw new TypeError('port (number or path) required') }
if (typeof (host) === 'function') {
callback = host
host = '127.0.0.1'
}
if (typeof (port) === 'string' && /^[0-9]+$/.test(port)) {
// Disambiguate between string ports and file paths
port = parseInt(port, 10)
}
const self = this
function cbListen () {
if (typeof (port) === 'number') {
self.host = self.address().address
self.port = self.address().port
} else {
self.host = port
self.port = self.server.fd
}
if (typeof (callback) === 'function') { callback() }
}
if (typeof (port) === 'number') {
return this.server.listen(port, host, cbListen)
} else {
return this.server.listen(port, cbListen)
}
}
Server.prototype.listenFD = function (fd) {
this.host = 'unix-domain-socket'
this.port = fd
return this.server.listenFD(fd)
}
Server.prototype.close = function (callback) {
return this.server.close(callback)
}
Server.prototype.address = function () {
return this.server.address()
}
Server.prototype.getConnections = function (callback) {
return this.server.getConnections(callback)
}
Server.prototype._getRoute = function (_dn, backend) {
if (!backend) { backend = this }
let name
if (Object.prototype.toString.call(_dn) === '[object LdapDn]') {
name = _dn.toString()
} else {
name = _dn
}
if (!this.routes[name]) {
this.routes[name] = {}
this.routes[name].backend = backend
this.routes[name].dn = _dn
// Force regeneration of the route key cache on next request
this._routeKeyCache = null
}
return this.routes[name]
}
Server.prototype._sortedRouteKeys = function _sortedRouteKeys () {
// The filtered/sorted route keys are cached to prevent needlessly
// regenerating the list for every incoming request.
if (!this._routeKeyCache) {
const self = this
const reversedRDNsToKeys = {}
// Generate mapping of reversedRDNs(DN) -> routeKey
Object.keys(this.routes).forEach(function (key) {
const _dn = self.routes[key].dn
// Ignore non-DN routes such as exop or unbind
if (Object.prototype.toString.call(_dn) === '[object LdapDn]') {
const reversed = _dn.clone()
reversed.reverse()
reversedRDNsToKeys[reversed.toString()] = key
}
})
const output = []
// Reverse-sort on reversedRDS(DN) in order to output routeKey list.
// This will place more specific DNs in front of their parents:
// 1. dc=test, dc=domain, dc=sub
// 2. dc=test, dc=domain
// 3. dc=other, dc=foobar
Object.keys(reversedRDNsToKeys).sort().reverse().forEach(function (_dn) {
output.push(reversedRDNsToKeys[_dn])
})
this._routeKeyCache = output
}
return this._routeKeyCache
}
Server.prototype._getHandlerChain = function _getHandlerChain (req) {
assert.ok(req)
const self = this
const routes = this.routes
let route
// check anonymous bind
if (req.protocolOp === Protocol.operations.LDAP_REQ_BIND &&
req.dn.toString() === '' &&
req.credentials === '') {
return {
backend: self,
handlers: [defaultNoOpHandler]
}
}
const op = '0x' + req.protocolOp.toString(16)
// Special cases are exops, unbinds and abandons. Handle those first.
if (req.protocolOp === Protocol.operations.LDAP_REQ_EXTENSION) {
route = routes[req.requestName]
if (route) {
return {
backend: route.backend,
handlers: (route[op] ? route[op] : [noExOpHandler])
}
} else {
return {
backend: self,
handlers: [noExOpHandler]
}
}
} else if (req.protocolOp === Protocol.operations.LDAP_REQ_UNBIND) {
route = routes.unbind
if (route) {
return {
backend: route.backend,
handlers: route[op]
}
} else {
return {
backend: self,
handlers: [defaultNoOpHandler]
}
}
} else if (req.protocolOp === Protocol.operations.LDAP_REQ_ABANDON) {
return {
backend: self,
handlers: [defaultNoOpHandler]
}
}
// Otherwise, match via DN rules
const keys = this._sortedRouteKeys()
let fallbackHandler = [noSuffixHandler]
// invalid DNs in non-strict mode are routed to the default handler
const testDN = (typeof (req.dn) === 'string') ? DN.fromString(req.dn) : req.dn
assert.ok(testDN)
for (let i = 0; i < keys.length; i++) {
const suffix = keys[i]
route = routes[suffix]
assert.ok(route.dn)
// Match a valid route or the route wildcard ('')
if (route.dn.equals(testDN) || route.dn.parentOf(testDN) || suffix === '') {
if (route[op]) {
// We should be good to go.
req.suffix = route.dn
return {
backend: route.backend,
handlers: route[op]
}
} else {
if (suffix === '') {
break
} else {
// We found a valid suffix but not a valid operation.
// There might be a more generic suffix with a legitimate operation.
fallbackHandler = [defaultHandler]
}
}
}
}
return {
backend: self,
handlers: fallbackHandler
}
}
Server.prototype._mount = function (op, name, argv, notDN) {
assert.ok(op)
assert.ok(name !== undefined)
assert.ok(argv)
if (typeof (name) !== 'string') { throw new TypeError('name (string) required') }
if (!argv.length) { throw new Error('at least one handler required') }
let backend = this
let index = 0
if (typeof (argv[0]) === 'object' && !Array.isArray(argv[0])) {
backend = argv[0]
index = 1
}
const route = this._getRoute(notDN ? name : DN.fromString(name), backend)
const chain = this._chain.slice()
argv.slice(index).forEach(function (a) {
chain.push(a)
})
route['0x' + op.toString(16)] = mergeFunctionArgs(chain)
return this
}

72
node_modules/ldapjs/lib/url.js generated vendored Normal file
View File

@ -0,0 +1,72 @@
'use strict'
const querystring = require('querystring')
const url = require('url')
const { DN } = require('@ldapjs/dn')
const filter = require('@ldapjs/filter')
module.exports = {
parse: function (urlStr, parseDN) {
let parsedURL
try {
parsedURL = new url.URL(urlStr)
} catch (error) {
throw new TypeError(urlStr + ' is an invalid LDAP url (scope)')
}
if (!parsedURL.protocol || !(parsedURL.protocol === 'ldap:' || parsedURL.protocol === 'ldaps:')) { throw new TypeError(urlStr + ' is an invalid LDAP url (protocol)') }
const u = {
protocol: parsedURL.protocol,
hostname: parsedURL.hostname,
port: parsedURL.port,
pathname: parsedURL.pathname,
search: parsedURL.search,
href: parsedURL.href
}
u.secure = (u.protocol === 'ldaps:')
if (!u.hostname) { u.hostname = 'localhost' }
if (!u.port) {
u.port = (u.secure ? 636 : 389)
} else {
u.port = parseInt(u.port, 10)
}
if (u.pathname) {
u.pathname = querystring.unescape(u.pathname.substr(1))
u.DN = parseDN ? DN.fromString(u.pathname) : u.pathname
}
if (u.search) {
u.attributes = []
const tmp = u.search.substr(1).split('?')
if (tmp && tmp.length) {
if (tmp[0]) {
tmp[0].split(',').forEach(function (a) {
u.attributes.push(querystring.unescape(a.trim()))
})
}
}
if (tmp[1]) {
if (tmp[1] !== 'base' && tmp[1] !== 'one' && tmp[1] !== 'sub') { throw new TypeError(urlStr + ' is an invalid LDAP url (scope)') }
u.scope = tmp[1]
}
if (tmp[2]) {
u.filter = querystring.unescape(tmp[2])
}
if (tmp[3]) {
u.extensions = querystring.unescape(tmp[3])
}
if (!u.scope) { u.scope = 'base' }
if (!u.filter) { u.filter = filter.parseString('(objectclass=*)') } else { u.filter = filter.parseString(u.filter) }
}
return u
}
}

59
node_modules/ldapjs/package.json generated vendored Normal file
View File

@ -0,0 +1,59 @@
{
"originalAuthor": "Mark Cavage <mcavage@gmail.com>",
"name": "ldapjs",
"homepage": "http://ldapjs.org",
"description": "LDAP client and server APIs",
"version": "3.0.7",
"license": "MIT",
"repository": {
"type": "git",
"url": "git://github.com/ldapjs/node-ldapjs.git"
},
"main": "lib/index.js",
"dependencies": {
"@ldapjs/asn1": "^2.0.0",
"@ldapjs/attribute": "^1.0.0",
"@ldapjs/change": "^1.0.0",
"@ldapjs/controls": "^2.1.0",
"@ldapjs/dn": "^1.1.0",
"@ldapjs/filter": "^2.1.1",
"@ldapjs/messages": "^1.3.0",
"@ldapjs/protocol": "^1.2.1",
"abstract-logging": "^2.0.1",
"assert-plus": "^1.0.0",
"backoff": "^2.5.0",
"once": "^1.4.0",
"vasync": "^2.2.1",
"verror": "^1.10.1"
},
"devDependencies": {
"@fastify/pre-commit": "^2.0.2",
"eslint": "^8.44.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^16.0.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "6.1.1",
"front-matter": "^4.0.2",
"get-port": "^5.1.1",
"highlight.js": "^11.7.0",
"marked": "^4.2.12",
"tap": "^16.3.7"
},
"scripts": {
"test": "tap --no-cov -R terse",
"test:ci": "tap --coverage-report=lcovonly -R terse",
"test:cov": "tap -R terse",
"test:cov:html": "tap --coverage-report=html -R terse",
"test:watch": "tap -n -w --no-coverage-report -R terse",
"test:integration": "tap --no-cov -R terse 'test-integration/**/*.test.js'",
"test:integration:local": "docker-compose up -d --wait && npm run test:integration ; docker-compose down",
"lint": "eslint . --fix",
"lint:ci": "eslint .",
"docs": "node scripts/build-docs.js"
},
"pre-commit": [
"lint:ci",
"test"
]
}

131
node_modules/ldapjs/scripts/build-docs.js generated vendored Normal file
View File

@ -0,0 +1,131 @@
const fs = require('fs/promises')
const path = require('path')
const { marked } = require('marked')
const fm = require('front-matter')
const { highlight } = require('highlight.js')
marked.use({
highlight: (code, lang) => {
if (lang) {
return highlight(code, { language: lang }).value
}
return code
}
})
function tocHTML (toc) {
let html = '<ul>\n'
for (const li of toc) {
html += '<li>\n'
html += `<div>\n<a href="#${li.slug}">${li.text}</a>\n</div>\n`
if (li.children && li.children.length > 0) {
html += tocHTML(li.children)
}
html += '</li>\n'
}
html += '</ul>\n'
return html
}
function markdownTOC (markdown) {
const tokens = marked.lexer(markdown)
const slugger = new marked.Slugger()
const toc = []
let currentHeading
let ignoreFirst = true
for (const token of tokens) {
if (token.type === 'heading') {
if (token.depth === 1) {
if (ignoreFirst) {
ignoreFirst = false
continue
}
currentHeading = {
text: token.text,
slug: slugger.slug(token.text),
children: []
}
toc.push(currentHeading)
} else if (token.depth === 2) {
if (!currentHeading) {
continue
}
currentHeading.children.push({
text: token.text,
slug: slugger.slug(token.text)
})
}
}
}
return {
toc: tocHTML(toc),
html: marked.parser(tokens)
}
}
function createHTML (template, text) {
const { attributes, body } = fm(text)
const { toc, html } = markdownTOC(body)
attributes.toc_html = toc
attributes.content = html
for (const prop in attributes) {
template = template.replace(new RegExp(`%\\(${prop}\\)s`, 'ig'), attributes[prop])
}
return template
}
async function copyRecursive (src, dest) {
const stats = await fs.stat(src)
const isDirectory = stats.isDirectory()
if (isDirectory) {
await fs.mkdir(dest)
const files = await fs.readdir(src)
for (const file of files) {
await copyRecursive(path.join(src, file), path.join(dest, file))
}
} else {
await fs.copyFile(src, dest)
}
}
async function createDocs () {
const docs = path.resolve(__dirname, '..', 'docs')
const dist = path.resolve(__dirname, '..', 'public')
const branding = path.join(docs, 'branding')
const src = path.join(branding, 'public')
try {
await fs.rm(dist, { recursive: true })
} catch (ex) {
if (ex.code !== 'ENOENT') {
throw ex
}
}
await copyRecursive(src, dist)
const highlightjsStyles = path.resolve(__dirname, '..', 'node_modules', 'highlight.js', 'styles')
await fs.copyFile(path.join(highlightjsStyles, 'default.css'), path.join(dist, 'media', 'css', 'highlight.css'))
const template = await fs.readFile(path.join(branding, 'template.html'), { encoding: 'utf8' })
const files = await fs.readdir(docs)
for (const file of files) {
if (!file.endsWith('.md')) {
continue
}
const text = await fs.readFile(path.join(docs, file), { encoding: 'utf8' })
const html = createHTML(template, text)
await fs.writeFile(path.join(dist, file.replace(/md$/, 'html')), html)
}
}
createDocs().catch(ex => {
console.error(ex)
process.exitCode = 1
})

5
node_modules/ldapjs/test-integration/.eslintrc.js generated vendored Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
rules: {
'no-shadow': 'off'
}
}

View File

@ -0,0 +1,21 @@
'use strict'
const tap = require('tap')
const ldapjs = require('../../lib')
const SCHEME = process.env.SCHEME || 'ldap'
const HOST = process.env.HOST || '127.0.0.1'
const PORT = process.env.PORT || 389
const baseURL = `${SCHEME}://${HOST}:${PORT}`
tap.test('connects to a server', t => {
t.plan(2)
const client = ldapjs.createClient({ url: baseURL })
client.bind('cn=Philip J. Fry,ou=people,dc=planetexpress,dc=com', 'fry', (err) => {
t.error(err)
t.pass()
client.unbind()
})
})

View File

@ -0,0 +1,95 @@
'use strict'
const tap = require('tap')
const ldapjs = require('../../lib')
const parseDN = ldapjs.parseDN
const SCHEME = process.env.SCHEME || 'ldap'
const HOST = process.env.HOST || '127.0.0.1'
const PORT = process.env.PORT || 389
const baseURL = `${SCHEME}://${HOST}:${PORT}`
const client = ldapjs.createClient({ url: baseURL })
tap.before(() => {
return new Promise((resolve, reject) => {
client.bind('cn=admin,dc=planetexpress,dc=com', 'GoodNewsEveryone', (err) => {
if (err) {
return reject(err)
}
resolve()
})
})
})
tap.teardown(() => {
client.unbind()
})
tap.test('can search OUs with Japanese characters', t => {
t.plan(2)
const opts = {
filter: '(&(objectClass=person))',
scope: 'sub',
paged: true,
sizeLimit: 100,
attributes: ['cn', 'employeeID']
}
const baseDN = parseDN('ou=テスト,dc=planetexpress,dc=com')
client.search(baseDN.toString(), opts, (err, res) => {
t.error(err, 'search error')
res.on('searchEntry', (entry) => {
t.match(entry.pojo, {
type: 'SearchResultEntry',
objectName: 'cn=jdoe,ou=\\e3\\83\\86\\e3\\82\\b9\\e3\\83\\88,dc=planetexpress,dc=com',
attributes: [{
type: 'cn',
values: ['John', 'jdoe']
}]
})
})
res.on('error', (err) => {
t.error(err, 'search entry error')
})
res.on('end', () => {
t.end()
})
})
})
tap.test('can search with non-ascii chars in filter', t => {
t.plan(3)
const opts = {
filter: '(&(sn=Rodríguez))',
scope: 'sub',
attributes: ['dn', 'sn', 'cn'],
type: 'user'
}
let searchEntryCount = 0
client.search('dc=planetexpress,dc=com', opts, (err, res) => {
t.error(err, 'search error')
res.on('searchEntry', (entry) => {
searchEntryCount += 1
t.match(entry.pojo, {
type: 'SearchResultEntry',
objectName: 'cn=Bender Bending Rodr\\c3\\adguez,ou=people,dc=planetexpress,dc=com',
attributes: [{
type: 'cn',
values: ['Bender Bending Rodríguez']
}]
})
})
res.on('error', (err) => {
t.error(err, 'search entry error')
})
res.on('end', () => {
t.equal(searchEntryCount, 1, 'should have found 1 entry')
t.end()
})
})
})

View File

@ -0,0 +1,56 @@
'use strict'
const tap = require('tap')
const ldapjs = require('../../lib')
const SCHEME = process.env.SCHEME || 'ldap'
const HOST = process.env.HOST || '127.0.0.1'
const PORT = process.env.PORT || 389
const baseURL = `${SCHEME}://${HOST}:${PORT}`
const client = ldapjs.createClient({ url: baseURL })
tap.test('adds entries with Korean characters', t => {
t.plan(4)
client.bind('cn=admin,dc=planetexpress,dc=com', 'GoodNewsEveryone', (err) => {
t.error(err, 'bind error')
})
const nm = '홍길동'
const dn = `cn=${nm},ou=people,dc=planetexpress,dc=com`
const entry = {
objectclass: 'person',
sn: 'korean test'
}
client.add(dn, entry, err => {
t.error(err, 'add entry error')
const searchOpts = {
filter: '(sn=korean test)',
scope: 'subtree',
attributes: ['cn', 'sn'],
sizeLimit: 10,
timeLimit: 0
}
client.search('ou=people,dc=planetexpress,dc=com', searchOpts, (err, res) => {
t.error(err, 'search error')
res.on('searchEntry', (entry) => {
t.equal(
entry.attributes.filter(a => a.type === 'cn').pop().values.pop(),
nm
)
})
res.on('error', (err) => {
t.error(err, 'search entry error')
})
res.on('end', () => {
client.unbind(t.end)
})
})
})
})

View File

@ -0,0 +1,55 @@
'use strict'
const tap = require('tap')
const ldapjs = require('../../lib')
const parseDN = ldapjs.parseDN
const SCHEME = process.env.SCHEME || 'ldap'
const HOST = process.env.HOST || '127.0.0.1'
const PORT = process.env.PORT || 389
const baseURL = `${SCHEME}://${HOST}:${PORT}`
const client = ldapjs.createClient({ url: baseURL })
const searchOpts = {
filter: '(&(objectClass=person))',
scope: 'sub',
paged: true,
sizeLimit: 0,
attributes: ['cn', 'employeeID']
}
const baseDN = parseDN('ou=large_ou,dc=planetexpress,dc=com')
tap.test('paged search option returns pages', t => {
t.plan(4)
client.bind('cn=admin,dc=planetexpress,dc=com', 'GoodNewsEveryone', (err) => {
t.error(err, 'bind error')
})
client.search(baseDN.toString(), searchOpts, (err, res) => {
t.error(err, 'search error')
let pages = 0
const results = []
res.on('searchEntry', (entry) => {
results.push(entry)
})
res.on('page', () => {
pages += 1
})
res.on('error', (err) => {
t.error(err, 'search entry error')
})
res.on('end', () => {
t.equal(results.length, 2000)
t.equal(pages, 20)
client.unbind(t.end)
})
})
})

View File

@ -0,0 +1,91 @@
'use strict'
const tap = require('tap')
const ldapjs = require('../../lib')
const { DN } = require('@ldapjs/dn')
const Change = require('@ldapjs/change')
const SCHEME = process.env.SCHEME || 'ldap'
const HOST = process.env.HOST || '127.0.0.1'
const PORT = process.env.PORT || 389
const baseURL = `${SCHEME}://${HOST}:${PORT}`
const client = ldapjs.createClient({ url: baseURL })
tap.teardown(() => {
client.unbind()
})
tap.test('modifies entry specified by dn string', t => {
t.plan(4)
client.bind('cn=admin,dc=planetexpress,dc=com', 'GoodNewsEveryone', (err) => {
t.error(err, 'bind error')
})
const dn = 'cn=large10,ou=large_ou,dc=planetexpress,dc=com'
const change = new Change({
operation: 'replace',
modification: {
type: 'givenName',
values: ['test']
}
})
client.modify(dn, change, (err) => {
t.error(err, 'modify error')
validateChange({ t, expected: 'test', client })
})
})
tap.test('modifies entry specified by dn object', t => {
t.plan(4)
client.bind('cn=admin,dc=planetexpress,dc=com', 'GoodNewsEveryone', (err) => {
t.error(err, 'bind error')
})
const dn = DN.fromString('cn=large10,ou=large_ou,dc=planetexpress,dc=com')
const change = new Change({
operation: 'replace',
modification: {
type: 'givenName',
values: ['test2']
}
})
client.modify(dn, change, (err) => {
t.error(err, 'modify error')
validateChange({ t, expected: 'test2', client })
})
})
function validateChange ({ t, expected, client }) {
const searchBase = 'ou=large_ou,dc=planetexpress,dc=com'
const searchOpts = {
filter: '(cn=large10)',
scope: 'subtree',
attributes: ['givenName'],
sizeLimit: 10,
timeLimit: 0
}
client.search(searchBase, searchOpts, (err, res) => {
t.error(err, 'search error')
res.on('searchEntry', entry => {
t.equal(
entry.attributes.filter(a => a.type === 'givenName').pop().values.pop(),
expected
)
})
res.on('error', err => {
t.error(err, 'search entry error')
})
res.on('end', () => {
t.end()
})
})
}

View File

@ -0,0 +1,81 @@
'use strict'
const tap = require('tap')
const ldapjs = require('../../lib')
const Change = require('@ldapjs/change')
const SCHEME = process.env.SCHEME || 'ldap'
const HOST = process.env.HOST || '127.0.0.1'
const PORT = process.env.PORT || 389
const baseURL = `${SCHEME}://${HOST}:${PORT}`
const client = ldapjs.createClient({ url: baseURL })
tap.before(() => {
return new Promise((resolve, reject) => {
client.bind('cn=admin,dc=planetexpress,dc=com', 'GoodNewsEveryone', (err) => {
if (err) {
return reject(err)
}
resolve()
})
})
})
tap.teardown(() => {
client.unbind()
})
tap.test('can modify entries with non-ascii chars in RDN', t => {
t.plan(6)
const dn = 'cn=Mendonça,ou=people,dc=planetexpress,dc=com'
const entry = {
objectclass: 'person',
sn: 'change me'
}
client.add(dn, entry, error => {
t.error(error, 'add should not error')
doSearch('change me', doModify)
})
function doModify () {
const change = new Change({
operation: 'replace',
modification: {
type: 'sn',
values: ['changed']
}
})
client.modify(dn, change, (error) => {
t.error(error, 'modify should not error')
doSearch('changed', t.end.bind(t))
})
}
function doSearch (expected, callback) {
const searchOpts = {
filter: '(&(objectclass=person)(cn=Mendonça))',
scope: 'subtree',
attributes: ['sn']
}
client.search('ou=people,dc=planetexpress,dc=com', searchOpts, (error, res) => {
t.error(error, 'search should not error')
res.on('searchEntry', entry => {
const found = entry.attributes.filter(a => a.type === 'sn').pop().values.pop()
t.equal(found, expected, `expected '${expected}' and got '${found}'`)
})
res.on('error', error => {
t.error(error, 'search result processing should not error')
})
res.on('end', () => {
callback()
})
})
}
})

View File

@ -0,0 +1,87 @@
'use strict'
const tap = require('tap')
const ldapjs = require('../../lib')
const SCHEME = process.env.SCHEME || 'ldap'
const HOST = process.env.HOST || '127.0.0.1'
const PORT = process.env.PORT || 389
const baseURL = `${SCHEME}://${HOST}:${PORT}`
tap.test('can use password policy response', t => {
const client = ldapjs.createClient({ url: baseURL })
const targetDN = 'cn=Bender Bending Rodríguez,ou=people,dc=planetexpress,dc=com'
client.bind('cn=admin,dc=planetexpress,dc=com', 'GoodNewsEveryone', (err, res) => {
t.error(err)
t.ok(res)
t.equal(res.status, 0)
const newPassword = 'bender2'
changePassword(client, newPassword, () => {
client.unbind()
bindNewClient(newPassword, { error: 2 }, (client) => {
const newPassword = 'bender'
changePassword(client, newPassword, () => {
client.unbind()
bindNewClient(newPassword, { timeBeforeExpiration: 1000 }, (client) => {
client.unbind(t.end)
})
})
})
})
})
function bindNewClient (pwd, expected, callback) {
const client = ldapjs.createClient({ url: baseURL })
const control = new ldapjs.PasswordPolicyControl()
client.bind(targetDN, pwd, control, (err, res) => {
t.error(err)
t.ok(res)
t.equal(res.status, 0)
let error = null
let timeBeforeExpiration = null
let graceAuthNsRemaining = null
res.controls.forEach(control => {
if (control.type === ldapjs.PasswordPolicyControl.OID) {
error = control.value.error ?? error
timeBeforeExpiration = control.value.timeBeforeExpiration ?? timeBeforeExpiration
graceAuthNsRemaining = control.value.graceAuthNsRemaining ?? graceAuthNsRemaining
}
})
if (expected.error !== undefined) {
t.equal(error, expected.error)
}
if (expected.timeBeforeExpiration !== undefined) {
t.equal(timeBeforeExpiration, expected.timeBeforeExpiration)
}
if (expected.graceAuthNsRemaining !== undefined) {
t.equal(graceAuthNsRemaining, expected.graceAuthNsRemaining)
}
callback(client)
})
}
function changePassword (client, newPwd, callback) {
const change = new ldapjs.Change({
operation: 'replace',
modification: new ldapjs.Attribute({
type: 'userPassword',
values: newPwd
})
})
client.modify(targetDN, change, (err, res) => {
t.error(err)
t.ok(res)
t.equal(res.status, 0)
callback()
})
}
})

View File

@ -0,0 +1,98 @@
'use strict'
const tap = require('tap')
const ldapjs = require('../../lib')
const SCHEME = process.env.SCHEME || 'ldap'
const HOST = process.env.HOST || '127.0.0.1'
const PORT = process.env.PORT || 389
const baseURL = `${SCHEME}://${HOST}:${PORT}`
tap.test('modifyDN with long name (issue #480)', t => {
// 2023-08-15: disabling this 265 character string until a bug can be
// fixed in OpenLDAP. See https://github.com/ldapjs/docker-test-openldap/blob/d48bc2fb001b4ed9a152715ced4a2cb120439ec4/bootstrap/slapd-init.sh#L19-L31.
// const longStr = 'a292979f2c86d513d48bbb9786b564b3c5228146e5ba46f404724e322544a7304a2b1049168803a5485e2d57a544c6a0d860af91330acb77e5907a9e601ad1227e80e0dc50abe963b47a004f2c90f570450d0e920d15436fdc771e3bdac0487a9735473ed3a79361d1778d7e53a7fb0e5f01f97a75ef05837d1d5496fc86968ff47fcb64'
// 2023-08-15: this 140 character string satisfies the original issue
// (https://github.com/ldapjs/node-ldapjs/issues/480) and avoids a bug
// in OpenLDAP 2.5.
const longStr = '292979f2c86d513d48bbb9786b564b3c5228146e5ba46f404724e322544a7304a2b1049168803a5485e2d57a544c6a0d860af91330acb77e5907a9e601ad1227e80e0dc50ab'
const targetDN = 'cn=Turanga Leela,ou=people,dc=planetexpress,dc=com'
const client = ldapjs.createClient({ url: baseURL })
client.bind('cn=admin,dc=planetexpress,dc=com', 'GoodNewsEveryone', bindHandler)
function bindHandler (err) {
t.error(err)
client.modifyDN(
targetDN,
`cn=${longStr},ou=people,dc=planetexpress,dc=com`,
modifyHandler
)
}
function modifyHandler (err, res) {
t.error(err)
t.ok(res)
t.equal(res.status, 0)
client.modifyDN(
`cn=${longStr},ou=people,dc=planetexpress,dc=com`,
targetDN,
(err) => {
t.error(err)
client.unbind(t.end)
}
)
}
})
tap.test('whois works correctly (issue #370)', t => {
const client = ldapjs.createClient({ url: baseURL })
client.bind('cn=Philip J. Fry,ou=people,dc=planetexpress,dc=com', 'fry', (err) => {
t.error(err)
client.exop('1.3.6.1.4.1.4203.1.11.3', (err, value, res) => {
t.error(err)
t.ok(value)
t.equal(value, 'dn:cn=Philip J. Fry,ou=people,dc=planetexpress,dc=com')
t.ok(res)
t.equal(res.status, 0)
client.unbind(t.end)
})
})
})
tap.test('can access large groups (issue #582)', t => {
const client = ldapjs.createClient({ url: baseURL })
client.bind('cn=admin,dc=planetexpress,dc=com ', 'GoodNewsEveryone', (err) => {
t.error(err)
const searchOpts = {
scope: 'sub',
filter: '(&(objectClass=group)(cn=large_group))'
}
client.search('ou=large_ou,dc=planetexpress,dc=com', searchOpts, (err, response) => {
t.error(err)
const results = []
response.on('searchEntry', (entry) => {
results.push(entry)
})
response.on('error', t.error)
response.on('end', (result) => {
t.equal(result.status, 0)
t.equal(results.length === 1, true)
t.ok(results[0].attributes)
const memberAttr = results[0].attributes.find(a => a.type === 'member')
t.ok(memberAttr)
t.ok(memberAttr.values)
t.type(memberAttr.values, Array)
t.equal(memberAttr.values.length, 2000)
client.unbind(t.end)
})
})
})
})

9
node_modules/ldapjs/test/.eslintrc.js generated vendored Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
'no-shadow': 'off'
}
}

1789
node_modules/ldapjs/test/client.test.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

53
node_modules/ldapjs/test/controls/control.test.js generated vendored Normal file
View File

@ -0,0 +1,53 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { Control, getControl } = require('../../lib')
test('new no args', function (t) {
t.ok(new Control())
t.end()
})
test('new with args', function (t) {
const c = new Control({
type: '2.16.840.1.113730.3.4.2',
criticality: true
})
t.ok(c)
t.equal(c.type, '2.16.840.1.113730.3.4.2')
t.ok(c.criticality)
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.startSequence()
ber.writeString('2.16.840.1.113730.3.4.2')
ber.writeBoolean(true)
ber.writeString('foo')
ber.endSequence()
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, '2.16.840.1.113730.3.4.2')
t.ok(c.criticality)
t.equal(c.value.toString('utf8'), 'foo')
t.end()
})
test('parse no value', function (t) {
const ber = new BerWriter()
ber.startSequence()
ber.writeString('2.16.840.1.113730.3.4.2')
ber.endSequence()
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, '2.16.840.1.113730.3.4.2')
t.equal(c.criticality, false)
t.notOk(c.value, null)
t.end()
})

104
node_modules/ldapjs/test/corked_emitter.test.js generated vendored Normal file
View File

@ -0,0 +1,104 @@
'use strict'
const { test } = require('tap')
const CorkedEmitter = require('../lib/corked_emitter')
function gatherEventSequence (expectedNumber) {
const gatheredEvents = []
let callback
const finished = new Promise(function (resolve) {
callback = function (...args) {
gatheredEvents.push(...args)
if (gatheredEvents.length >= expectedNumber) {
// Prevent result mutation after our promise is resolved:
resolve(gatheredEvents.slice())
}
}
})
return {
finished,
callback
}
}
test('normal emit flow', function (t) {
const emitter = new CorkedEmitter()
const expectedSequence = [
['searchEntry', { data: 'a' }],
['searchEntry', { data: 'b' }],
['end']
]
const gatherer = gatherEventSequence(3)
emitter.on('searchEntry', function (...args) {
gatherer.callback(['searchEntry', ...args])
})
emitter.on('end', function (...args) {
gatherer.callback(['end', ...args])
})
emitter.emit('searchEntry', { data: 'a' })
emitter.emit('searchEntry', { data: 'b' })
emitter.emit('end')
gatherer.finished.then(function (gatheredEvents) {
expectedSequence.forEach(function (expectedEvent, i) {
t.equal(JSON.stringify(expectedEvent), JSON.stringify(gatheredEvents[i]))
})
t.end()
})
})
test('reversed listener registration', function (t) {
const emitter = new CorkedEmitter()
const expectedSequence = [
['searchEntry', { data: 'a' }],
['searchEntry', { data: 'b' }],
['end']
]
const gatherer = gatherEventSequence(3)
// This time, we swap the event listener registrations.
// The order of emits should remain unchanged.
emitter.on('end', function (...args) {
gatherer.callback(['end', ...args])
})
emitter.on('searchEntry', function (...args) {
gatherer.callback(['searchEntry', ...args])
})
emitter.emit('searchEntry', { data: 'a' })
emitter.emit('searchEntry', { data: 'b' })
emitter.emit('end')
gatherer.finished.then(function (gatheredEvents) {
expectedSequence.forEach(function (expectedEvent, i) {
t.equal(JSON.stringify(expectedEvent), JSON.stringify(gatheredEvents[i]))
})
t.end()
})
})
test('delayed listener registration', function (t) {
const emitter = new CorkedEmitter()
const expectedSequence = [
['searchEntry', { data: 'a' }],
['searchEntry', { data: 'b' }],
['end']
]
const gatherer = gatherEventSequence(3)
emitter.emit('searchEntry', { data: 'a' })
emitter.emit('searchEntry', { data: 'b' })
emitter.emit('end')
// The listeners only appear after a brief delay - this simulates
// the situation described in https://github.com/ldapjs/node-ldapjs/issues/602
// and in https://github.com/ifroz/node-ldapjs/commit/5239f6c68827f2c25b4589089c199d15bb882412
setTimeout(function () {
emitter.on('end', function (...args) {
gatherer.callback(['end', ...args])
})
emitter.on('searchEntry', function (...args) {
gatherer.callback(['searchEntry', ...args])
})
}, 50)
gatherer.finished.then(function (gatheredEvents) {
expectedSequence.forEach(function (expectedEvent, i) {
t.equal(JSON.stringify(expectedEvent), JSON.stringify(gatheredEvents[i]))
})
t.end()
})
})

53
node_modules/ldapjs/test/errors.test.js generated vendored Normal file
View File

@ -0,0 +1,53 @@
'use strict'
const { test } = require('tap')
const {
LDAPError,
ConnectionError,
AbandonedError,
TimeoutError,
ConstraintViolationError,
LDAP_OTHER
} = require('../lib')
test('basic error', function (t) {
const msg = 'mymsg'
const err = new LDAPError(msg, null, null)
t.ok(err)
t.equal(err.name, 'LDAPError')
t.equal(err.code, LDAP_OTHER)
t.equal(err.dn, '')
t.equal(err.message, msg)
t.end()
})
test('exports ConstraintViolationError', function (t) {
const msg = 'mymsg'
const err = new ConstraintViolationError(msg, null, null)
t.ok(err)
t.equal(err.name, 'ConstraintViolationError')
t.equal(err.code, 19)
t.equal(err.dn, '')
t.equal(err.message, msg)
t.end()
})
test('"custom" errors', function (t) {
const errors = [
{ name: 'ConnectionError', Func: ConnectionError },
{ name: 'AbandonedError', Func: AbandonedError },
{ name: 'TimeoutError', Func: TimeoutError }
]
errors.forEach(function (entry) {
const msg = entry.name + 'msg'
const err = new entry.Func(msg)
t.ok(err)
t.equal(err.name, entry.name)
t.equal(err.code, LDAP_OTHER)
t.equal(err.dn, '')
t.equal(err.message, msg)
})
t.end()
})

BIN
node_modules/ldapjs/test/imgs/test.jpg generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

116
node_modules/ldapjs/test/issue-845.test.js generated vendored Normal file
View File

@ -0,0 +1,116 @@
'use strict'
const tap = require('tap')
const { SearchResultEntry, SearchRequest } = require('@ldapjs/messages')
const ldapjs = require('../')
const server = ldapjs.createServer()
const SUFFIX = ''
const directory = {
'dc=example,dc=com': {
objectclass: 'example',
dc: 'example',
cn: 'example'
}
}
server.bind(SUFFIX, (req, res, done) => {
res.end()
return done()
})
server.search(SUFFIX, (req, res, done) => {
const dn = req.dn.toString().toLowerCase()
if (Object.hasOwn(directory, dn) === false) {
return done(Error('not in directory'))
}
switch (req.scope) {
case SearchRequest.SCOPE_BASE:
case SearchRequest.SCOPE_SUBTREE: {
res.send(new SearchResultEntry({ objectName: `dc=${req.scopeName}` }))
break
}
}
res.end()
done()
})
tap.beforeEach(t => {
return new Promise((resolve, reject) => {
server.listen(0, '127.0.0.1', (err) => {
if (err) return reject(err)
t.context.url = server.url
t.context.client = ldapjs.createClient({ url: [server.url] })
t.context.searchOpts = {
filter: '(&(objectClass=*))',
scope: 'sub',
attributes: ['dn', 'cn']
}
resolve()
})
})
})
tap.afterEach(t => {
return new Promise((resolve, reject) => {
t.context.client.destroy()
server.close((err) => {
if (err) return reject(err)
resolve()
})
})
})
tap.test('rejects if search not in directory', t => {
const { client, searchOpts } = t.context
client.search('dc=nope', searchOpts, (err, res) => {
t.error(err)
res.on('error', err => {
// TODO: plain error messages should not be lost
// This should be fixed in a revamp of the server code.
// ~ jsumners 2023-03-08
t.equal(err.lde_message, 'Operations Error')
t.end()
})
})
})
tap.test('base scope matches', t => {
const { client, searchOpts } = t.context
searchOpts.scope = 'base'
client.search('dc=example,dc=com', searchOpts, (err, res) => {
t.error(err)
res.on('error', (err) => {
t.error(err)
t.end()
})
res.on('searchEntry', entry => {
t.equal(entry.objectName.toString(), 'dc=base')
t.end()
})
})
})
tap.test('sub scope matches', t => {
const { client, searchOpts } = t.context
client.search('dc=example,dc=com', searchOpts, (err, res) => {
t.error(err)
res.on('error', (err) => {
t.error(err)
t.end()
})
res.on('searchEntry', entry => {
t.equal(entry.objectName.toString(), 'dc=subtree')
t.end()
})
})
})

103
node_modules/ldapjs/test/issue-890.test.js generated vendored Normal file
View File

@ -0,0 +1,103 @@
'use strict'
// This test is complicated. It must simulate a server sending an unsolicited,
// or a mismatched, message in order to force the client's internal message
// tracker to try and find a corresponding sent message that does not exist.
// In order to do that, we need to set a high test timeout and wait for the
// error message to be logged.
const tap = require('tap')
const ldapjs = require('../')
const { SearchResultEntry } = require('@ldapjs/messages')
const server = ldapjs.createServer()
const SUFFIX = ''
tap.timeout = 10000
server.bind(SUFFIX, (res, done) => {
res.end()
return done()
})
server.search(SUFFIX, (req, res, done) => {
const result = new SearchResultEntry({
objectName: `dc=${req.scopeName}`
})
// Respond to the search request with a matched response.
res.send(result)
res.end()
// After a short delay, send ANOTHER response to the client that will not
// be matched by the client's internal tracker.
setTimeout(
() => {
res.send(result)
res.end()
done()
},
100
)
})
tap.beforeEach(t => {
return new Promise((resolve, reject) => {
server.listen(0, '127.0.0.1', (err) => {
if (err) return reject(err)
t.context.logMessages = []
t.context.logger = {
child () { return this },
debug () {},
error (...args) {
t.context.logMessages.push(args)
},
trace () {}
}
t.context.url = server.url
t.context.client = ldapjs.createClient({
url: [server.url],
timeout: 5,
log: t.context.logger
})
resolve()
})
})
})
tap.afterEach(t => {
return new Promise((resolve, reject) => {
t.context.client.destroy()
server.close((err) => {
if (err) return reject(err)
resolve()
})
})
})
tap.test('handle null messages', t => {
const { client, logMessages } = t.context
// There's no way to get an error from the client when it has received an
// unmatched response from the server. So we need to poll our logger instance
// and detect when the corresponding error message has been logged.
const timer = setInterval(
() => {
if (logMessages.length > 0) {
t.equal(
logMessages.some(msg => msg[1] === 'unmatched server message received'),
true
)
clearInterval(timer)
t.end()
}
},
100
)
client.search('dc=test', (error) => {
t.error(error)
})
})

143
node_modules/ldapjs/test/laundry.test.js generated vendored Normal file
View File

@ -0,0 +1,143 @@
'use strict'
const tap = require('tap')
const { getSock, uuid } = require('./utils')
const { SearchResultEntry } = require('@ldapjs/messages')
const Attribute = require('@ldapjs/attribute')
const ldap = require('../lib')
function search (t, options, callback) {
t.context.client.search(t.context.suffix, options, function (err, res) {
t.error(err)
t.ok(res)
let found = false
res.on('searchEntry', function (entry) {
t.ok(entry)
found = true
})
res.on('end', function () {
t.ok(found)
if (callback) return callback()
return t.end()
})
})
}
tap.beforeEach((t) => {
return new Promise((resolve, reject) => {
const suffix = `dc=${uuid()}`
const server = ldap.createServer()
t.context.server = server
t.context.socketPath = getSock()
t.context.suffix = suffix
server.on('error', err => {
server.close(() => reject(err))
})
server.bind('cn=root', function (req, res, next) {
res.end()
return next()
})
server.search(suffix, function (req, res) {
const entry = new SearchResultEntry({
entry: 'cn=foo,' + suffix,
attributes: Attribute.fromObject({
objectclass: ['person', 'top'],
cn: 'Pogo Stick',
sn: 'Stick',
givenname: 'ogo',
mail: uuid() + '@pogostick.org'
})
})
if (req.filter.matches(entry.attributes)) {
res.send(entry)
}
res.end()
})
server.listen(t.context.socketPath, function () {
t.context.client = ldap.createClient({
socketPath: t.context.socketPath
})
t.context.client.on('error', (err) => {
t.context.server.close(() => reject(err))
})
t.context.client.on('connectError', (err) => {
t.context.server.close(() => reject(err))
})
t.context.client.on('connect', (socket) => {
t.context.socket = socket
resolve()
})
})
})
})
tap.afterEach((t) => {
return new Promise((resolve, reject) => {
if (!t.context.client) return resolve()
t.context.client.unbind(() => {
t.context.server.close((err) => {
if (err) return reject(err)
resolve()
})
})
})
})
tap.test('Evolution search filter (GH-3)', function (t) {
// This is what Evolution sends, when searching for a contact 'ogo'. Wow.
const filter =
'(|(cn=ogo*)(givenname=ogo*)(sn=ogo*)(mail=ogo*)(member=ogo*)' +
'(primaryphone=ogo*)(telephonenumber=ogo*)(homephone=ogo*)(mobile=ogo*)' +
'(carphone=ogo*)(facsimiletelephonenumber=ogo*)' +
'(homefacsimiletelephonenumber=ogo*)(otherphone=ogo*)' +
'(otherfacsimiletelephonenumber=ogo*)(internationalisdnnumber=ogo*)' +
'(pager=ogo*)(radio=ogo*)(telex=ogo*)(assistantphone=ogo*)' +
'(companyphone=ogo*)(callbackphone=ogo*)(tty=ogo*)(o=ogo*)(ou=ogo*)' +
'(roomnumber=ogo*)(title=ogo*)(businessrole=ogo*)(managername=ogo*)' +
'(assistantname=ogo*)(postaladdress=ogo*)(l=ogo*)(st=ogo*)' +
'(postofficebox=ogo*)(postalcode=ogo*)(c=ogo*)(homepostaladdress=ogo*)' +
'(mozillahomelocalityname=ogo*)(mozillahomestate=ogo*)' +
'(mozillahomepostalcode=ogo*)(mozillahomecountryname=ogo*)' +
'(otherpostaladdress=ogo*)(jpegphoto=ogo*)(usercertificate=ogo*)' +
'(labeleduri=ogo*)(displayname=ogo*)(spousename=ogo*)(note=ogo*)' +
'(anniversary=ogo*)(birthdate=ogo*)(mailer=ogo*)(fileas=ogo*)' +
'(category=ogo*)(calcaluri=ogo*)(calfburl=ogo*)(icscalendar=ogo*))'
return search(t, filter)
})
tap.test('GH-49 Client errors on bad attributes', function (t) {
const searchOpts = {
filter: 'cn=*ogo*',
scope: 'one',
attributes: 'dn'
}
return search(t, searchOpts)
})
tap.test('GH-55 Client emits connect multiple times', function (t) {
const c = ldap.createClient({
socketPath: t.context.socketPath
})
let count = 0
c.on('connect', function (socket) {
t.ok(socket)
count++
c.bind('cn=root', 'secret', function (err) {
t.error(err)
c.unbind(function () {
t.equal(count, 1)
t.end()
})
})
})
})

View File

@ -0,0 +1,47 @@
'use strict'
const { test } = require('tap')
const { MAX_MSGID } = require('../../../../lib/client/constants')
const geWindow = require('../../../../lib/client/message-tracker/ge-window')
test('comp > (ref in upper window) => true', async t => {
const ref = Math.floor(MAX_MSGID / 2) + 10
const comp = ref + 10
const result = geWindow(ref, comp)
t.equal(result, true)
})
test('comp < (ref in upper window) => false', async t => {
const ref = Math.floor(MAX_MSGID / 2) + 10
const comp = ref - 5
const result = geWindow(ref, comp)
t.equal(result, false)
})
test('comp > (ref in lower window) => true', async t => {
const ref = Math.floor(MAX_MSGID / 2) - 10
const comp = ref + 20
const result = geWindow(ref, comp)
t.equal(result, true)
})
test('comp < (ref in lower window) => false', async t => {
const ref = Math.floor(MAX_MSGID / 2) - 10
const comp = ref - 5
const result = geWindow(ref, comp)
t.equal(result, false)
})
test('(max === MAX_MSGID) && (comp > ref) => true', async t => {
const ref = MAX_MSGID - Math.floor(MAX_MSGID / 2)
const comp = ref + 1
const result = geWindow(ref, comp)
t.equal(result, true)
})
test('(max === MAX_MSGID) && (comp < ref) => false', async t => {
const ref = MAX_MSGID - Math.floor(MAX_MSGID / 2)
const comp = ref - 1
const result = geWindow(ref, comp)
t.equal(result, false)
})

View File

@ -0,0 +1,21 @@
'use strict'
const { test } = require('tap')
const { MAX_MSGID } = require('../../../../lib/client/constants')
const idGeneratorFactory = require('../../../../lib/client/message-tracker/id-generator')
test('starts at 0', async t => {
const nextID = idGeneratorFactory()
const currentID = nextID()
t.equal(currentID, 1)
})
test('handles wrapping around', async t => {
const nextID = idGeneratorFactory(MAX_MSGID - 2)
let currentID = nextID()
t.equal(currentID, MAX_MSGID - 1)
currentID = nextID()
t.equal(currentID, 1)
})

View File

@ -0,0 +1,201 @@
'use strict'
const tap = require('tap')
const messageTrackerFactory = require('../../../../lib/client/message-tracker/')
tap.test('options', t => {
t.test('requires an options object', async t => {
try {
messageTrackerFactory()
} catch (error) {
t.match(error, /options object is required/)
}
try {
messageTrackerFactory([])
} catch (error) {
t.match(error, /options object is required/)
}
try {
messageTrackerFactory('')
} catch (error) {
t.match(error, /options object is required/)
}
try {
messageTrackerFactory(42)
} catch (error) {
t.match(error, /options object is required/)
}
})
t.test('requires id to be a string', async t => {
try {
messageTrackerFactory({ id: {} })
} catch (error) {
t.match(error, /options\.id string is required/)
}
try {
messageTrackerFactory({ id: [] })
} catch (error) {
t.match(error, /options\.id string is required/)
}
try {
messageTrackerFactory({ id: 42 })
} catch (error) {
t.match(error, /options\.id string is required/)
}
})
t.test('requires parser to be an object', async t => {
try {
messageTrackerFactory({ id: 'foo', parser: 'bar' })
} catch (error) {
t.match(error, /options\.parser object is required/)
}
try {
messageTrackerFactory({ id: 'foo', parser: 42 })
} catch (error) {
t.match(error, /options\.parser object is required/)
}
try {
messageTrackerFactory({ id: 'foo', parser: [] })
} catch (error) {
t.match(error, /options\.parser object is required/)
}
})
t.end()
})
tap.test('.pending', t => {
t.test('returns 0 for no messages', async t => {
const tracker = messageTrackerFactory({ id: 'foo', parser: {} })
t.equal(tracker.pending, 0)
})
t.test('returns 1 for 1 message', async t => {
const tracker = messageTrackerFactory({ id: 'foo', parser: {} })
tracker.track({}, () => {})
t.equal(tracker.pending, 1)
})
t.end()
})
tap.test('#abandon', t => {
t.test('returns false if message does not exist', async t => {
const tracker = messageTrackerFactory({ id: 'foo', parser: {} })
const result = tracker.abandon(1)
t.equal(result, false)
})
t.test('returns true if message is abandoned', async t => {
const tracker = messageTrackerFactory({ id: 'foo', parser: {} })
tracker.track({}, {})
const result = tracker.abandon(1)
t.equal(result, true)
})
t.end()
})
tap.test('#fetch', t => {
t.test('returns handler for fetched message', async t => {
const tracker = messageTrackerFactory({ id: 'foo', parser: {} })
tracker.track({}, handler)
const { callback: fetched } = tracker.fetch(1)
t.equal(fetched, handler)
function handler () {}
})
t.test('returns handler for fetched abandoned message', async t => {
const tracker = messageTrackerFactory({ id: 'foo', parser: {} })
tracker.track({}, handler)
tracker.track({ abandon: 'message' }, () => {})
tracker.abandon(1)
const { callback: fetched } = tracker.fetch(1)
t.equal(fetched, handler)
function handler () {}
})
t.test('returns null when message does not exist', async t => {
const tracker = messageTrackerFactory({ id: 'foo', parser: {} })
const fetched = tracker.fetch(1)
t.equal(fetched, null)
})
t.end()
})
tap.test('#purge', t => {
t.test('invokes cb for each tracked message', async t => {
t.plan(4)
let count = 0
const tracker = messageTrackerFactory({ id: 'foo', parser: {} })
tracker.track({}, handler1)
tracker.track({}, handler2)
tracker.purge(cb)
function cb (msgID, handler) {
if (count === 0) {
t.equal(msgID, 1)
t.equal(handler, handler1)
count += 1
return
}
t.equal(msgID, 2)
t.equal(handler, handler2)
}
function handler1 () {}
function handler2 () {}
})
t.end()
})
tap.test('#remove', t => {
t.test('removes from the current track', async t => {
const tracker = messageTrackerFactory({ id: 'foo', parser: {} })
tracker.track({}, () => {})
tracker.remove(1)
t.equal(tracker.pending, 0)
})
// Not a great test. It exercises the desired code path, but we probably
// should expose some insight into the abandoned track.
t.test('removes from the abandoned track', async t => {
const tracker = messageTrackerFactory({ id: 'foo', parser: {} })
tracker.track({}, () => {})
tracker.track({ abandon: 'message' }, () => {})
tracker.abandon(1)
tracker.remove(1)
t.equal(tracker.pending, 1)
})
t.end()
})
tap.test('#track', t => {
t.test('add messageId and tracks message', async t => {
const tracker = messageTrackerFactory({ id: 'foo', parser: {} })
const msg = {}
tracker.track(msg, handler)
t.same(msg, { messageId: 1 })
const { callback: cb } = tracker.fetch(1)
t.equal(cb, handler)
function handler () {}
})
t.end()
})

View File

@ -0,0 +1,64 @@
'use strict'
const { test } = require('tap')
const { MAX_MSGID } = require('../../../../lib/client/constants')
const purgeAbandoned = require('../../../../lib/client/message-tracker/purge-abandoned')
test('clears queue if only one message present', async t => {
t.plan(3)
const abandoned = new Map()
abandoned.set(1, { age: 2, cb })
purgeAbandoned(2, abandoned)
t.equal(abandoned.size, 0)
function cb (err) {
t.equal(err.name, 'AbandonedError')
t.equal(err.message, 'client request abandoned')
}
})
test('clears queue if multiple messages present', async t => {
t.plan(5)
const abandoned = new Map()
abandoned.set(1, { age: 2, cb })
abandoned.set(2, { age: 3, cb })
purgeAbandoned(4, abandoned)
t.equal(abandoned.size, 0)
function cb (err) {
t.equal(err.name, 'AbandonedError')
t.equal(err.message, 'client request abandoned')
}
})
test('message id has wrappred around', async t => {
t.plan(3)
const abandoned = new Map()
abandoned.set(MAX_MSGID - 1, { age: MAX_MSGID, cb })
// The "abandon" message was sent with an id of "MAX_MSGID". So the message
// that is triggering the purge was the "first" message in the new sequence
// of message identifiers.
purgeAbandoned(1, abandoned)
t.equal(abandoned.size, 0)
function cb (err) {
t.equal(err.name, 'AbandonedError')
t.equal(err.message, 'client request abandoned')
}
})
test('does not clear if window not met', async t => {
t.plan(1)
const abandoned = new Map()
abandoned.set(1, { age: 2, cb })
purgeAbandoned(1, abandoned)
t.equal(abandoned.size, 1)
function cb () {
t.fail('should not be invoked')
}
})

View File

@ -0,0 +1,82 @@
'use strict'
const { test } = require('tap')
const enqueue = require('../../../../lib/client/request-queue/enqueue')
test('rejects new requests if size is exceeded', async t => {
const q = { _queue: { size: 5 }, size: 5 }
const result = enqueue.call(q, 'foo', 'bar', {}, {})
t.notOk(result)
})
test('rejects new requests if queue is frozen', async t => {
const q = { _queue: { size: 0 }, size: 5, _frozen: true }
const result = enqueue.call(q, 'foo', 'bar', {}, {})
t.notOk(result)
})
test('adds a request and returns if no timeout', async t => {
const q = {
_queue: {
size: 0,
add (obj) {
t.same(obj, {
message: 'foo',
expect: 'bar',
emitter: 'baz',
cb: 'bif'
})
}
},
_frozen: false,
timeout: 0
}
const result = enqueue.call(q, 'foo', 'bar', 'baz', 'bif')
t.ok(result)
})
test('adds a request and returns timer not set', async t => {
const q = {
_queue: {
size: 0,
add (obj) {
t.same(obj, {
message: 'foo',
expect: 'bar',
emitter: 'baz',
cb: 'bif'
})
}
},
_frozen: false,
timeout: 100,
_timer: null
}
const result = enqueue.call(q, 'foo', 'bar', 'baz', 'bif')
t.ok(result)
})
test('adds a request, returns true, and clears queue', t => {
// Must not be an async test due to an internal `setTimeout`
t.plan(4)
const q = {
_queue: {
size: 0,
add (obj) {
t.same(obj, {
message: 'foo',
expect: 'bar',
emitter: 'baz',
cb: 'bif'
})
}
},
_frozen: false,
timeout: 5,
_timer: 123,
freeze () { t.pass() },
purge () { t.pass() }
}
const result = enqueue.call(q, 'foo', 'bar', 'baz', 'bif')
t.ok(result)
})

View File

@ -0,0 +1,51 @@
'use strict'
const { test } = require('tap')
const flush = require('../../../../lib/client/request-queue/flush')
test('clears timer', async t => {
t.plan(2)
const q = {
_timer: 123,
_queue: {
values () {
return []
},
clear () {
t.pass()
}
}
}
flush.call(q)
t.equal(q._timer, null)
})
test('invokes callback with parameters', async t => {
t.plan(6)
const req = {
message: 'foo',
expect: 'bar',
emitter: 'baz',
cb: theCB
}
const q = {
_timer: 123,
_queue: {
values () {
return [req]
},
clear () {
t.pass()
}
}
}
flush.call(q, (message, expect, emitter, cb) => {
t.equal(message, 'foo')
t.equal(expect, 'bar')
t.equal(emitter, 'baz')
t.equal(cb, theCB)
})
t.equal(q._timer, null)
function theCB () {}
})

View File

@ -0,0 +1,18 @@
'use strict'
const { test } = require('tap')
const purge = require('../../../../lib/client/request-queue/purge')
test('flushes the queue with timeout errors', async t => {
t.plan(3)
const q = {
flush (func) {
func('a', 'b', 'c', (err) => {
t.ok(err)
t.equal(err.name, 'TimeoutError')
t.equal(err.message, 'request queue timeout')
})
}
}
purge.call(q)
})

16
node_modules/ldapjs/test/messages/parser.test.js generated vendored Normal file
View File

@ -0,0 +1,16 @@
'use strict'
const { test } = require('tap')
const { Parser } = require('../../lib')
test('wrong protocol error', function (t) {
const p = new Parser()
p.once('error', function (err) {
t.ok(err)
t.end()
})
// Send some bogus data to incur an error
p.write(Buffer.from([16, 1, 4]))
})

471
node_modules/ldapjs/test/server.test.js generated vendored Normal file
View File

@ -0,0 +1,471 @@
'use strict'
const net = require('net')
const tap = require('tap')
const vasync = require('vasync')
const vm = require('node:vm')
const { getSock } = require('./utils')
const ldap = require('../lib')
const SERVER_PORT = process.env.SERVER_PORT || 1389
const SUFFIX = 'dc=test'
tap.beforeEach(function (t) {
// We do not need a `.afterEach` to clean up the sock files because that
// is done when the server is destroyed.
t.context.sock = getSock()
})
tap.test('basic create', function (t) {
const server = ldap.createServer()
t.ok(server)
t.end()
})
tap.test('connection count', function (t) {
const server = ldap.createServer()
t.ok(server)
server.listen(0, '127.0.0.1', function () {
t.ok(true, 'server listening on ' + server.url)
server.getConnections(function (err, count) {
t.error(err)
t.equal(count, 0)
const client = ldap.createClient({ url: server.url })
client.on('connect', function () {
t.ok(true, 'client connected')
server.getConnections(function (err, count) {
t.error(err)
t.equal(count, 1)
client.unbind()
server.close(() => t.end())
})
})
})
})
})
tap.test('properties', function (t) {
const server = ldap.createServer()
t.equal(server.name, 'LDAPServer')
// TODO: better test
server.maxConnections = 10
t.equal(server.maxConnections, 10)
t.equal(server.url, null, 'url empty before bind')
// listen on a random port so we have a url
server.listen(0, '127.0.0.1', function () {
t.ok(server.url)
server.close(() => t.end())
})
})
tap.test('IPv6 URL is formatted correctly', function (t) {
const server = ldap.createServer()
t.equal(server.url, null, 'url empty before bind')
server.listen(0, '::1', function () {
t.ok(server.url)
t.equal(server.url, 'ldap://[::1]:' + server.port)
server.close(() => t.end())
})
})
tap.test('listen on unix/named socket', function (t) {
const server = ldap.createServer()
server.listen(t.context.sock, function () {
t.ok(server.url)
t.equal(server.url.split(':')[0], 'ldapi')
server.close(() => t.end())
})
})
tap.test('listen on static port', function (t) {
const server = ldap.createServer()
server.listen(SERVER_PORT, '127.0.0.1', function () {
const addr = server.address()
t.equal(addr.port, parseInt(SERVER_PORT, 10))
t.equal(server.url, `ldap://127.0.0.1:${SERVER_PORT}`)
server.close(() => t.end())
})
})
tap.test('listen on ephemeral port', function (t) {
const server = ldap.createServer()
server.listen(0, '127.0.0.1', function () {
const addr = server.address()
t.ok(addr.port > 0)
t.ok(addr.port < 65535)
server.close(() => t.end())
})
})
tap.test('route order', function (t) {
function generateHandler (response) {
const func = function handler (req, res, next) {
res.send({
dn: response,
attributes: { }
})
res.end()
return next()
}
return func
}
const server = ldap.createServer()
const sock = t.context.sock
const dnShort = SUFFIX
const dnMed = 'dc=sub,' + SUFFIX
const dnLong = 'dc=long,dc=sub,' + SUFFIX
// Mount routes out of order
server.search(dnMed, generateHandler(dnMed))
server.search(dnShort, generateHandler(dnShort))
server.search(dnLong, generateHandler(dnLong))
server.listen(sock, function () {
t.ok(true, 'server listen')
const client = ldap.createClient({ socketPath: sock })
client.on('connect', () => {
vasync.forEachParallel({
func: runSearch,
inputs: [dnShort, dnMed, dnLong]
}, function (err) {
t.error(err)
client.unbind()
server.close(() => t.end())
})
})
function runSearch (value, cb) {
client.search(value, '(objectclass=*)', function (err, res) {
t.error(err)
t.ok(res)
res.on('searchEntry', function (entry) {
t.equal(entry.dn.toString(), value)
})
res.on('end', function () {
cb()
})
})
}
})
})
tap.test('route absent', function (t) {
const server = ldap.createServer()
const DN_ROUTE = 'dc=base'
const DN_MISSING = 'dc=absent'
server.bind(DN_ROUTE, function (req, res, next) {
res.end()
return next()
})
server.listen(t.context.sock, function () {
t.ok(true, 'server startup')
vasync.parallel({
funcs: [
function presentBind (cb) {
const clt = ldap.createClient({ socketPath: t.context.sock })
clt.bind(DN_ROUTE, '', function (err) {
t.notOk(err)
clt.unbind()
cb()
})
},
function absentBind (cb) {
const clt = ldap.createClient({ socketPath: t.context.sock })
clt.bind(DN_MISSING, '', function (err) {
t.ok(err)
t.equal(err.code, ldap.LDAP_NO_SUCH_OBJECT)
clt.unbind()
cb()
})
}
]
}, function (err) {
t.notOk(err)
server.close(() => t.end())
})
})
})
tap.test('route unbind', function (t) {
const server = ldap.createServer()
server.unbind(function (req, res, next) {
t.ok(true, 'server unbind successful')
res.end()
return next()
})
server.listen(t.context.sock, function () {
t.ok(true, 'server startup')
const client = ldap.createClient({ socketPath: t.context.sock })
client.bind('', '', function (err) {
t.error(err, 'client bind error')
client.unbind(function (err) {
t.error(err, 'client unbind error')
server.close(() => t.end())
})
})
})
})
tap.test('bind/unbind identity anonymous', function (t) {
const server = ldap.createServer({
connectionRouter: function (c) {
server.newConnection(c)
server.emit('testconnection', c)
}
})
server.unbind(function (req, res, next) {
t.ok(true, 'server unbind successful')
res.end()
return next()
})
server.bind('', function (req, res, next) {
t.ok(true, 'server bind successful')
res.end()
return next()
})
const anonDN = ldap.parseDN('cn=anonymous')
server.listen(t.context.sock, function () {
t.ok(true, 'server startup')
const client = ldap.createClient({ socketPath: t.context.sock })
server.once('testconnection', (c) => {
t.ok(anonDN.equals(c.ldap.bindDN), 'pre bind dn is correct')
client.bind('', '', function (err) {
t.error(err, 'client anon bind error')
t.ok(anonDN.equals(c.ldap.bindDN), 'anon bind dn is correct')
client.unbind(function (err) {
t.error(err, 'client anon unbind error')
t.ok(anonDN.equals(c.ldap.bindDN), 'anon unbind dn is correct')
server.close(() => t.end())
})
})
})
})
})
tap.test('does not crash on empty DN values', function (t) {
const server = ldap.createServer({
connectionRouter: function (c) {
server.newConnection(c)
server.emit('testconnection', c)
}
})
server.listen(t.context.sock, function () {
const client = ldap.createClient({ socketPath: t.context.sock })
server.once('testconnection', () => {
client.bind('', 'pw', function (err) {
t.ok(err, 'blank bind dn throws error')
client.unbind(function () {
server.close(() => t.end())
})
})
})
})
})
tap.test('bind/unbind identity user', function (t) {
const server = ldap.createServer({
connectionRouter: function (c) {
server.newConnection(c)
server.emit('testconnection', c)
}
})
server.unbind(function (req, res, next) {
t.ok(true, 'server unbind successful')
res.end()
return next()
})
server.bind('', function (req, res, next) {
t.ok(true, 'server bind successful')
res.end()
return next()
})
const anonDN = ldap.parseDN('cn=anonymous')
const testDN = ldap.parseDN('cn=anotheruser')
server.listen(t.context.sock, function () {
t.ok(true, 'server startup')
const client = ldap.createClient({ socketPath: t.context.sock })
server.once('testconnection', (c) => {
t.ok(anonDN.equals(c.ldap.bindDN), 'pre bind dn is correct')
client.bind(testDN.toString(), 'somesecret', function (err) {
t.error(err, 'user bind error')
t.ok(testDN.equals(c.ldap.bindDN), 'user bind dn is correct')
// check rebinds too
client.bind('', '', function (err) {
t.error(err, 'client anon bind error')
t.ok(anonDN.equals(c.ldap.bindDN), 'anon bind dn is correct')
// user rebind
client.bind(testDN.toString(), 'somesecret', function (err) {
t.error(err, 'user bind error')
t.ok(testDN.equals(c.ldap.bindDN), 'user rebind dn is correct')
client.unbind(function (err) {
t.error(err, 'user unbind error')
t.ok(anonDN.equals(c.ldap.bindDN), 'user unbind dn is correct')
server.close(() => t.end())
})
})
})
})
})
})
})
tap.test('strict routing', function (t) {
const testDN = 'cn=valid'
let clt
let server
const sock = t.context.sock
vasync.pipeline({
funcs: [
function setup (_, cb) {
server = ldap.createServer({})
// invalid DNs would go to default handler
server.search('', function (req, res, next) {
t.ok(req.dn)
t.equal(typeof (req.dn), 'object')
t.equal(req.dn.toString(), testDN)
res.end()
next()
})
server.listen(sock, function () {
t.ok(true, 'server startup')
clt = ldap.createClient({
socketPath: sock
})
cb()
})
},
function testGood (_, cb) {
clt.search(testDN, { scope: 'base' }, function (err, res) {
t.error(err)
res.once('error', function (err2) {
t.error(err2)
cb(err2)
})
res.once('end', function (result) {
t.ok(result, 'accepted invalid dn')
cb()
})
})
}
]
}, function (err) {
t.error(err)
if (clt) {
clt.destroy()
}
server.close(() => t.end())
})
})
tap.test('close accept a callback', function (t) {
const server = ldap.createServer()
// callback is called when the server is closed
server.listen(0, function (err) {
t.error(err)
server.close(function (err) {
t.error(err)
t.end()
})
})
})
tap.test('close without error calls callback', function (t) {
const server = ldap.createServer()
// when the server is closed without error, the callback parameter is undefined
server.listen(1389, '127.0.0.1', function (err) {
t.error(err)
server.close(function (err) {
t.error(err)
t.end()
})
})
})
tap.test('close passes error to callback', function (t) {
const server = ldap.createServer()
// when the server is closed with an error, the error is the first parameter of the callback
server.close(function (err) {
t.ok(err)
t.end()
})
})
tap.test('multithreading support via external server', function (t) {
const serverOptions = { }
const server = ldap.createServer(serverOptions)
const fauxServer = net.createServer(serverOptions, (connection) => {
server.newConnection(connection)
})
fauxServer.log = serverOptions.log
fauxServer.ldap = {
config: serverOptions
}
t.ok(server)
fauxServer.listen(5555, '127.0.0.1', function () {
t.ok(true, 'server listening on ' + server.url)
t.ok(fauxServer)
const client = ldap.createClient({ url: 'ldap://127.0.0.1:5555' })
client.on('connect', function () {
t.ok(client)
client.unbind()
fauxServer.close(() => t.end())
})
})
})
tap.test('multithreading support via hook', function (t) {
const serverOptions = {
connectionRouter: (connection) => {
server.newConnection(connection)
}
}
const server = ldap.createServer(serverOptions)
const fauxServer = ldap.createServer(serverOptions)
t.ok(server)
fauxServer.listen(0, '127.0.0.1', function () {
t.ok(true, 'server listening on ' + server.url)
t.ok(fauxServer)
const client = ldap.createClient({ url: fauxServer.url })
client.on('connect', function () {
t.ok(client)
client.unbind()
fauxServer.close(() => t.end())
})
})
})
tap.test('cross-realm type checks', function (t) {
const server = ldap.createServer()
const ctx = vm.createContext({})
vm.runInContext(
'globalThis.search=function(){};\n' +
'globalThis.searches=[function(){}];'
, ctx)
server.search('', ctx.search)
server.search('', ctx.searches)
t.ok(server)
t.end()
})

58
node_modules/ldapjs/test/url.test.js generated vendored Normal file
View File

@ -0,0 +1,58 @@
'use strict'
const { test } = require('tap')
const { parseURL } = require('../lib')
test('parse empty', function (t) {
const u = parseURL('ldap:///')
t.equal(u.hostname, 'localhost')
t.equal(u.port, 389)
t.ok(!u.DN)
t.ok(!u.attributes)
t.equal(u.secure, false)
t.end()
})
test('parse hostname', function (t) {
const u = parseURL('ldap://example.com/')
t.equal(u.hostname, 'example.com')
t.equal(u.port, 389)
t.ok(!u.DN)
t.ok(!u.attributes)
t.equal(u.secure, false)
t.end()
})
test('parse host and port', function (t) {
const u = parseURL('ldap://example.com:1389/')
t.equal(u.hostname, 'example.com')
t.equal(u.port, 1389)
t.ok(!u.DN)
t.ok(!u.attributes)
t.equal(u.secure, false)
t.end()
})
test('parse full', function (t) {
const u = parseURL('ldaps://ldap.example.com:1389/dc=example%20,dc=com' +
'?cn,sn?sub?(cn=Babs%20Jensen)')
t.equal(u.secure, true)
t.equal(u.hostname, 'ldap.example.com')
t.equal(u.port, 1389)
t.equal(u.DN, 'dc=example ,dc=com')
t.ok(u.attributes)
t.equal(u.attributes.length, 2)
t.equal(u.attributes[0], 'cn')
t.equal(u.attributes[1], 'sn')
t.equal(u.scope, 'sub')
t.equal(u.filter.toString(), '(cn=Babs Jensen)')
t.end()
})
test('supports href', function (t) {
const u = parseURL('ldaps://ldap.example.com:1389/dc=example%20,dc=com?cn,sn?sub?(cn=Babs%20Jensen)')
t.equal(u.href, 'ldaps://ldap.example.com:1389/dc=example%20,dc=com?cn,sn?sub?(cn=Babs%20Jensen)')
t.end()
})

22
node_modules/ldapjs/test/utils.js generated vendored Normal file
View File

@ -0,0 +1,22 @@
'use strict'
const os = require('os')
const path = require('path')
const crypto = require('crypto')
function uuid () {
return crypto.randomBytes(16).toString('hex')
}
function getSock () {
if (process.platform === 'win32') {
return '\\\\.\\pipe\\' + uuid()
} else {
return path.join(os.tmpdir(), uuid())
}
}
module.exports = {
getSock,
uuid
}