First commit
This commit is contained in:
4
node_modules/ldapjs/.eslintignore
generated
vendored
Normal file
4
node_modules/ldapjs/.eslintignore
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
coverage/
|
||||
.nyc_output/
|
||||
docs/
|
||||
20
node_modules/ldapjs/.eslintrc.js
generated
vendored
Normal file
20
node_modules/ldapjs/.eslintrc.js
generated
vendored
Normal 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
18
node_modules/ldapjs/.github/dependabot.yml
generated
vendored
Normal 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
30
node_modules/ldapjs/.github/workflows/docs.yml
generated
vendored
Normal 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
39
node_modules/ldapjs/.github/workflows/integration.yml
generated
vendored
Normal 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
47
node_modules/ldapjs/.github/workflows/main.yml
generated
vendored
Normal 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
10
node_modules/ldapjs/.taprc.yml
generated
vendored
Normal 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
104
node_modules/ldapjs/CHANGES.md
generated
vendored
Normal 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
19
node_modules/ldapjs/LICENSE
generated
vendored
Normal 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
71
node_modules/ldapjs/README.md
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
# LDAPjs
|
||||
|
||||
[](https://github.com/ldapjs/node-ldapjs/actions)
|
||||
[](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
10
node_modules/ldapjs/docker-compose.yml
generated
vendored
Normal 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
1
node_modules/ldapjs/docs/branding/public/CNAME
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
ldapjs.org
|
||||
266
node_modules/ldapjs/docs/branding/public/media/css/style.css
generated
vendored
Normal file
266
node_modules/ldapjs/docs/branding/public/media/css/style.css
generated
vendored
Normal file
@ -0,0 +1,266 @@
|
||||
|
||||
/* ---- general styles */
|
||||
|
||||
body {
|
||||
font: 13px "Lucida Grande", "Lucida Sans Unicode", arial, sans-serif;
|
||||
line-height: 1.53846; /* 20px */
|
||||
color: #4a3f2d;
|
||||
}
|
||||
|
||||
:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
h1,h2,h3 {
|
||||
font-weight:normal;
|
||||
}
|
||||
|
||||
h3{
|
||||
margin-bottom:0;
|
||||
}
|
||||
|
||||
ul, li {
|
||||
margin:0px;
|
||||
padding:0px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-left:40px;
|
||||
}
|
||||
|
||||
ul > li {
|
||||
list-style:disc;
|
||||
list-style-position:inside;
|
||||
margin:10px 0px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border:none;
|
||||
width:98%;
|
||||
margin-left:-10px;
|
||||
border-top:1px solid #CCCCCC;
|
||||
border-bottom:1px solid #FFFFFF;
|
||||
}
|
||||
|
||||
code,
|
||||
pre {
|
||||
border:1px solid #CCCCCC;
|
||||
background:#F2F0EE;
|
||||
-webkit-border-radius:2px;
|
||||
-moz-border-radius:2px;
|
||||
border-radius:2px;
|
||||
white-space:pre-wrap;
|
||||
}
|
||||
code {
|
||||
padding: 0 0.2em;
|
||||
}
|
||||
pre {
|
||||
margin: 1em 0;
|
||||
padding: .75em;
|
||||
overflow: auto;
|
||||
padding:10px 1.2em;
|
||||
margin-top:0;
|
||||
margin-bottom:20px;
|
||||
}
|
||||
pre code {
|
||||
border: medium none;
|
||||
padding: 0;
|
||||
}
|
||||
a code {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a {
|
||||
color:#FD6512;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 85%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1em;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* ---- header and sidebar */
|
||||
|
||||
#header {
|
||||
background:#C3BDB3;
|
||||
background:#1C313C;
|
||||
height:66px;
|
||||
left:0px;
|
||||
position:absolute;
|
||||
top:0px;
|
||||
width:100%;
|
||||
z-index:1;
|
||||
font-size:0.7em;
|
||||
}
|
||||
|
||||
#header h1 {
|
||||
width: 424px;
|
||||
height: 35px;
|
||||
display:block;
|
||||
background: url(../img/logo.svg) no-repeat;
|
||||
line-height:2.1em;
|
||||
padding:0;
|
||||
padding-left:140px;
|
||||
margin-top:18px;
|
||||
margin-left:20px;
|
||||
color:white;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
background-color:#EDEBEA;
|
||||
bottom:0px;
|
||||
left:0px;
|
||||
overflow:auto;
|
||||
padding:20px 0px 0px 15px;
|
||||
position:absolute;
|
||||
top:66px;
|
||||
width:265px;
|
||||
z-index:1;
|
||||
}
|
||||
|
||||
#content {
|
||||
top:64px;
|
||||
bottom:0px;
|
||||
right:0px;
|
||||
left:290px;
|
||||
padding:20px 30px 400px;
|
||||
position:absolute;
|
||||
overflow:auto;
|
||||
z-index:0;
|
||||
}
|
||||
|
||||
#sidebar h1 {
|
||||
font-size:1.2em;
|
||||
padding:0px;
|
||||
margin-top:15px;
|
||||
margin-bottom:3px;
|
||||
}
|
||||
|
||||
#sidebar ul {
|
||||
margin:3px 0 10px 0;
|
||||
}
|
||||
|
||||
#sidebar ul ul {
|
||||
margin:3px 0 5px 10px;
|
||||
}
|
||||
|
||||
#sidebar li {
|
||||
margin:0;
|
||||
padding:0;
|
||||
font-size:0.9em;
|
||||
}
|
||||
|
||||
#sidebar li,
|
||||
#sidebar li a {
|
||||
color:#5C5954;
|
||||
list-style:none;
|
||||
padding:1px 0px 1px 2px;
|
||||
}
|
||||
|
||||
|
||||
/* ---- intro */
|
||||
|
||||
.intro {
|
||||
color:#29231A;
|
||||
padding: 22px 25px;
|
||||
background: #EDEBEA;
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-o-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
margin-bottom:40px;
|
||||
}
|
||||
.intro h1 {
|
||||
color: #1C313C;
|
||||
}
|
||||
.intro h3 {
|
||||
margin: 5px 0px 3px;
|
||||
font-size: 100%;
|
||||
font-weight: bold;
|
||||
}
|
||||
.intro ul {
|
||||
list-style-type:disc;
|
||||
padding-left:20px;
|
||||
margin-left:0;
|
||||
}
|
||||
.intro ul li{
|
||||
margin:0;
|
||||
}
|
||||
.intro p {
|
||||
padding-left:20px;
|
||||
margin: 5px 0px 3px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
h2 {
|
||||
overflow: auto;
|
||||
margin-top: 60px;
|
||||
border-top: 2px solid #979592;
|
||||
z-index: 3;
|
||||
}
|
||||
h1 + h2 {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
h2 span {
|
||||
background: #979592;
|
||||
float:right;
|
||||
color:#fff;
|
||||
margin:0;
|
||||
margin-left:3px;
|
||||
padding:0.3em 0.7em;
|
||||
font-size: 0.55em;
|
||||
word-spacing: 0.8em; /* separate verb from path */
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*---- print media */
|
||||
|
||||
@media print {
|
||||
body { background:white; color:black; margin:0; }
|
||||
#sidebar {
|
||||
display: none;
|
||||
}
|
||||
#content {
|
||||
position: relative;
|
||||
padding: 5px;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
h1, h2, h4 {
|
||||
page-break-after: avoid;
|
||||
}
|
||||
pre {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
|
||||
/* tables still need cellspacing="0" in the markup */
|
||||
table {
|
||||
border-collapse:collapse; border-spacing:0;
|
||||
margin: 20px 0;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
border: solid #aaa;
|
||||
border-width: 1px 0;
|
||||
line-height: 23px;
|
||||
padding: 0 12px;
|
||||
text-align: left;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
th {
|
||||
border-collapse: separate;
|
||||
}
|
||||
tbody tr:nth-child(odd) {
|
||||
background-color: #f2f0ee;
|
||||
}
|
||||
1
node_modules/ldapjs/docs/branding/public/media/img/logo.svg
generated
vendored
Normal file
1
node_modules/ldapjs/docs/branding/public/media/img/logo.svg
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="125" height="34.1" viewBox="0 0 146.25 39.96"><defs><path d="M-2.21-3.96h150v45.93h-150z"/><clipPath><use xlink:href="#a" overflow="visible"/></clipPath><clipPath><use xlink:href="#c-4" overflow="visible"/></clipPath></defs><clipPath><use xlink:href="#a" overflow="visible"/></clipPath><g clip-path="url(#b-8)" fill="#f60"><defs><path d="m-2.21-3.96 150 0 0 45.93-150 0z"/></defs><clipPath><use xlink:href="#c-4" overflow="visible" x="0" y="0" width="100" height="100"/></clipPath><path d="m15.74 31.29c8.61 0 15.6-6.98 15.6-15.59C31.34 7.08 24.35 0.1 15.74 0.1 7.13 0.1 0.14 7.08 0.14 15.7c0 8.61 6.98 15.6 15.6 15.6" style="fill-rule:evenodd;fill:#f60"/><path d="m12.96 7.35c0-0.32 0.26-0.59 0.59-0.59l4.38 0c0.33 0 0.59 0.26 0.59 0.59l0 5.57 5.57 0c0.33 0 0.59 0.26 0.59 0.59l0 4.38c0 0.33-0.26 0.59-0.59 0.59l-5.57 0 0 5.57c0 0.33-0.26 0.59-0.59 0.59l-4.37 0c-0.32 0-0.59-0.26-0.59-0.59l0-5.57-5.57 0c-0.32 0-0.59-0.26-0.59-0.59l0-4.37c0-0.32 0.26-0.59 0.59-0.59l5.57 0z" style="fill-rule:evenodd;fill:#fff"/></g><g clip-path="url(#b)" fill="#fff"><defs><path d="m-2.21-3.96 150 0 0 45.93-150 0z"/></defs><clipPath><use height="100" width="100" y="0" x="0" overflow="visible" xlink:href="#c"/></clipPath><path d="m35.84 25.26c1.22 0.94 2.63 1.81 4.52 1.81 2.87 0 3.62-1.73 3.62-4.01l0-19.96 3.11 0 0 16.27c0 1.42 0 2.95-0.08 4.36-0.16 3.77-2.08 5.97-6.52 5.97-3.06 0-5.31-1.26-6.21-2.24zm29.51-5.7c0-4.72-1.37-8.02-5.19-8.02-3.73 0-5.42 3.14-5.42 7.39 0 3.89 0.79 8.49 5.27 8.49 3.73 0 5.35-3.57 5.35-7.86M60.41 9.14c2.71 0 8.06 0.87 8.06 9.75 0 7.66-3.81 10.89-8.72 10.89-4.99 0-8.06-3.38-8.06-10.45 0-8.13 4.83-10.18 8.72-10.18m26.88 19.3 0-18.74-2.99 0 0 11.71c0 2.71-1.81 4.6-4.79 4.6-4.17 0-4.44-2.28-4.44-5.27l0-11.04-3.06 0 0 11.87c0 4.48 2.16 6.09 5.5 6.45-1.93 1.26-4.32 3.54-4.32 6.72 0 3.03 1.93 4.95 5.93 4.95 4.17 0 6.68-2 7.66-5.82 0.35-1.38 0.51-3.69 0.51-5.42m-7.86 8.88c3.58 0 4.83-3.1 4.83-7.39l0-3.42c-4.72 1.97-8.13 4.09-8.13 7.82 0 1.93 1.14 2.99 3.3 2.99M99.94 9.14c-5.97 0-9.12 4.79-9.12 10.61 0 5.86 2.59 10.02 8.72 10.02 3.14 0 5.42-1.41 6.41-2.24l-1.18-2c-0.75 0.55-2.4 1.81-5.03 1.81-4.16 0-5.74-3.38-5.89-6.6l1.02 0.04c3.81 0 10.81-0.94 10.81-6.76 0-2.91-2.08-4.87-5.74-4.87m-0.16 2.28c-4.2 0-5.82 3.97-5.93 7.07l0.79 0.04c2.67 0 8.17-0.63 8.17-4.17 0-1.85-1.26-2.95-3.03-2.95m13.01 17.8 0-12.81c0-2.36 2.28-4.83 5.31-4.83 3.3 0 3.93 2.36 3.93 5.27l0 12.38 3.07 0 0-13.32c0-4.48-2.2-6.76-6.25-6.76-2.56 0-4.83 1.18-6.33 3.38l-0.35-2.83-2.63 0 0.2 2.95 0 16.58 3.07 0zm16.82-27.31 0 21.97c0 2.87 0.16 5.9 5.38 5.9 1.57 0 3.3-0.51 4.44-1.3l-0.9-2.04c-0.67 0.39-1.65 0.94-2.99 0.94-1.85 0-2.87-0.82-2.87-3.42l0-11.63 5.58 0 0-2.63-5.58 0 0-7.78zm12.22 1.8c0 1.14 0.91 1.99 2 1.99 1.08 0 1.99-0.85 1.99-1.99 0-1.12-0.91-1.97-1.99-1.97-1.09 0-2 0.85-2 1.97m0.36 0c0-0.95 0.71-1.68 1.64-1.68 0.92 0 1.63 0.73 1.63 1.68 0 0.97-0.71 1.7-1.63 1.7-0.93 0-1.64-0.73-1.64-1.7m0.86 1.17 0.36 0 0-1 0.38 0 0.63 1 0.39 0-0.66-1.02c0.35-0.04 0.61-0.21 0.61-0.63 0-0.44-0.26-0.66-0.81-0.66l-0.9 0 0 2.32zm0.36-2.02 0.48 0c0.24 0 0.51 0.05 0.51 0.36 0 0.37-0.29 0.38-0.61 0.38l-0.38 0z" clip-path="url(#d)" style="fill-rule:evenodd;fill:#fff"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
39
node_modules/ldapjs/docs/branding/template.html
generated
vendored
Normal file
39
node_modules/ldapjs/docs/branding/template.html
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>%(title)s</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<link rel="stylesheet" type="text/css" href="media/css/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="media/css/highlight.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<h1>%(title)s Documentation</h1>
|
||||
</div>
|
||||
<div id="sidebar">
|
||||
|
||||
<div>Sections</div>
|
||||
<span>
|
||||
<ul>
|
||||
<li><div><a href="index.html">Home</a></div></li>
|
||||
<li><div><a href="guide.html">Guide</a></div></li>
|
||||
<li><div><a href="examples.html">Examples</a></div></li>
|
||||
<li><div><a href="client.html">Client API</a></div></li>
|
||||
<li><div><a href="server.html">Server API</a></div></li>
|
||||
<li><div><a href="dn.html">DN API</a></div></li>
|
||||
<li><div><a href="filters.html">Filters API</a></div></li>
|
||||
<li><div><a href="errors.html">Error API</a></div></li>
|
||||
</ul>
|
||||
</span>
|
||||
|
||||
<div>Contents</div>
|
||||
</span>
|
||||
%(toc_html)s
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
%(content)s
|
||||
</div><!-- end #content -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
491
node_modules/ldapjs/docs/client.md
generated
vendored
Normal file
491
node_modules/ldapjs/docs/client.md
generated
vendored
Normal file
@ -0,0 +1,491 @@
|
||||
---
|
||||
title: Client API | ldapjs
|
||||
---
|
||||
|
||||
# ldapjs Client API
|
||||
|
||||
<div class="intro">
|
||||
|
||||
This document covers the ldapjs client API and assumes that you are familiar
|
||||
with LDAP. If you're not, read the [guide](guide.html) first.
|
||||
|
||||
</div>
|
||||
|
||||
# Create a client
|
||||
|
||||
The code to create a new client looks like:
|
||||
|
||||
```js
|
||||
const ldap = require('ldapjs');
|
||||
|
||||
const client = ldap.createClient({
|
||||
url: ['ldap://127.0.0.1:1389', 'ldap://127.0.0.2:1389']
|
||||
});
|
||||
|
||||
client.on('connectError', (err) => {
|
||||
// handle connection error
|
||||
})
|
||||
```
|
||||
|
||||
You can use `ldap://` or `ldaps://`; the latter would connect over SSL (note
|
||||
that this will not use the LDAP TLS extended operation, but literally an SSL
|
||||
connection to port 636, as in LDAP v2). The full set of options to create a
|
||||
client is:
|
||||
|
||||
|Attribute |Description |
|
||||
|---------------|-----------------------------------------------------------|
|
||||
|url |A string or array of valid LDAP URL(s) (proto/host/port) |
|
||||
|socketPath |Socket path if using AF\_UNIX sockets |
|
||||
|log |A compatible logger instance (Default: no-op logger) |
|
||||
|timeout |Milliseconds client should let operations live for before timing out (Default: Infinity)|
|
||||
|connectTimeout |Milliseconds client should wait before timing out on TCP connections (Default: OS default)|
|
||||
|tlsOptions |Additional options passed to TLS connection layer when connecting via `ldaps://` (See: The TLS docs for node.js)|
|
||||
|idleTimeout |Milliseconds after last activity before client emits idle event|
|
||||
|reconnect |Try to reconnect when the connection gets lost (Default is false)|
|
||||
|
||||
### url
|
||||
This parameter takes a single connection string or an array of connection strings
|
||||
as an input. In case an array is provided, the client tries to connect to the
|
||||
servers in given order. To achieve random server strategy (e.g. to distribute
|
||||
the load among the servers), please shuffle the array before passing it as an
|
||||
argument.
|
||||
|
||||
### Note On Logger
|
||||
|
||||
A passed in logger is expected to conform to the [Bunyan](https://www.npmjs.com/package/bunyan)
|
||||
API. Specifically, the logger is expected to have a `child()` method. If a logger
|
||||
is supplied that does not have such a method, then a shim version is added
|
||||
that merely returns the passed in logger.
|
||||
|
||||
Known compatible loggers are:
|
||||
|
||||
+ [Bunyan](https://www.npmjs.com/package/bunyan)
|
||||
+ [Pino](https://www.npmjs.com/package/pino)
|
||||
|
||||
|
||||
### Note On Error Handling
|
||||
|
||||
The client is an `EventEmitter`. If you don't register an error handler and
|
||||
e.g. a connection error occurs, Node.js will print a stack trace and exit the
|
||||
process ([reference](https://nodejs.org/api/events.html#error-events)).
|
||||
|
||||
## Connection management
|
||||
|
||||
As LDAP is a stateful protocol (as opposed to HTTP), having connections torn
|
||||
down from underneath you can be difficult to deal with. Several mechanisms
|
||||
have been provided to mitigate this trouble.
|
||||
|
||||
### Reconnect
|
||||
|
||||
You can provide a Boolean option indicating if a reconnect should be tried. For
|
||||
more sophisticated control, you can provide an Object with the properties
|
||||
`initialDelay` (default: `100`), `maxDelay` (default: `10000`) and
|
||||
`failAfter` (default: `Infinity`).
|
||||
After the reconnect you maybe need to [bind](#bind) again.
|
||||
|
||||
## Client events
|
||||
|
||||
The client is an `EventEmitter` and can emit the following events:
|
||||
|
||||
|Event |Description |
|
||||
|---------------|----------------------------------------------------------|
|
||||
|error |General error |
|
||||
|connectRefused |Server refused connection. Most likely bad authentication |
|
||||
|connectTimeout |Server timeout |
|
||||
|connectError |Socket connection error |
|
||||
|setupError |Setup error after successful connection |
|
||||
|socketTimeout |Socket timeout |
|
||||
|resultError |Search result error |
|
||||
|timeout |Search result timeout |
|
||||
|destroy |After client is disconnected |
|
||||
|end |Socket end event |
|
||||
|close |Socket closed |
|
||||
|connect |Client connected |
|
||||
|idle |Idle timeout reached |
|
||||
|
||||
## Common patterns
|
||||
|
||||
The last two parameters in every API are `controls` and `callback`. `controls`
|
||||
can be either a single instance of a `Control` or an array of `Control` objects.
|
||||
You can, and probably will, omit this option.
|
||||
|
||||
Almost every operation has the callback form of `function(err, res)` where err
|
||||
will be an instance of an `LDAPError` (you can use `instanceof` to switch).
|
||||
You probably won't need to check the `res` parameter, but it's there if you do.
|
||||
|
||||
# bind
|
||||
`bind(dn, password, controls, callback)`
|
||||
|
||||
Performs a bind operation against the LDAP server.
|
||||
|
||||
The bind API only allows LDAP 'simple' binds (equivalent to HTTP Basic
|
||||
Authentication) for now. Note that all client APIs can optionally take an array
|
||||
of `Control` objects. You probably don't need them though...
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
client.bind('cn=root', 'secret', (err) => {
|
||||
assert.ifError(err);
|
||||
});
|
||||
```
|
||||
|
||||
# add
|
||||
`add(dn, entry, controls, callback)`
|
||||
|
||||
Performs an add operation against the LDAP server.
|
||||
|
||||
Allows you to add an entry (which is just a plain JS object), and as always,
|
||||
controls are optional.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
const entry = {
|
||||
cn: 'foo',
|
||||
sn: 'bar',
|
||||
email: ['foo@bar.com', 'foo1@bar.com'],
|
||||
objectclass: 'fooPerson'
|
||||
};
|
||||
client.add('cn=foo, o=example', entry, (err) => {
|
||||
assert.ifError(err);
|
||||
});
|
||||
```
|
||||
|
||||
# compare
|
||||
`compare(dn, attribute, value, controls, callback)`
|
||||
|
||||
Performs an LDAP compare operation with the given attribute and value against
|
||||
the entry referenced by dn.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
client.compare('cn=foo, o=example', 'sn', 'bar', (err, matched) => {
|
||||
assert.ifError(err);
|
||||
|
||||
console.log('matched: ' + matched);
|
||||
});
|
||||
```
|
||||
|
||||
# del
|
||||
`del(dn, controls, callback)`
|
||||
|
||||
|
||||
Deletes an entry from the LDAP server.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
client.del('cn=foo, o=example', (err) => {
|
||||
assert.ifError(err);
|
||||
});
|
||||
```
|
||||
|
||||
# exop
|
||||
`exop(name, value, controls, callback)`
|
||||
|
||||
Performs an LDAP extended operation against an LDAP server. `name` is typically
|
||||
going to be an OID (well, the RFC says it must be; however, ldapjs has no such
|
||||
restriction). `value` is completely arbitrary, and is whatever the exop says it
|
||||
should be.
|
||||
|
||||
Example (performs an LDAP 'whois' extended op):
|
||||
|
||||
```js
|
||||
client.exop('1.3.6.1.4.1.4203.1.11.3', (err, value, res) => {
|
||||
assert.ifError(err);
|
||||
|
||||
console.log('whois: ' + value);
|
||||
});
|
||||
```
|
||||
|
||||
# modify
|
||||
`modify(name, changes, controls, callback)`
|
||||
|
||||
Performs an LDAP modify operation against the LDAP server. This API requires
|
||||
you to pass in a `Change` object, which is described below. Note that you can
|
||||
pass in a single `Change` or an array of `Change` objects.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
const change = new ldap.Change({
|
||||
operation: 'add',
|
||||
modification: {
|
||||
type: 'pets',
|
||||
values: ['cat', 'dog']
|
||||
}
|
||||
});
|
||||
|
||||
client.modify('cn=foo, o=example', change, (err) => {
|
||||
assert.ifError(err);
|
||||
});
|
||||
```
|
||||
|
||||
## Change
|
||||
|
||||
A `Change` object maps to the LDAP protocol of a modify change, and requires you
|
||||
to set the `operation` and `modification`. The `operation` is a string, and
|
||||
must be one of:
|
||||
|
||||
| Operation | Description |
|
||||
|-----------|-------------|
|
||||
| replace | Replaces the attribute referenced in `modification`. If the modification has no values, it is equivalent to a delete. |
|
||||
| add | Adds the attribute value(s) referenced in `modification`. The attribute may or may not already exist. |
|
||||
| delete | Deletes the attribute (and all values) referenced in `modification`. |
|
||||
|
||||
`modification` is just a plain old JS object with the required type and values you want.
|
||||
|
||||
| Operation | Description |
|
||||
|-----------|-------------|
|
||||
| type | String that defines the attribute type for the modification. |
|
||||
| values | Defines the values for modification. |
|
||||
|
||||
|
||||
# modifyDN
|
||||
`modifyDN(dn, newDN, controls, callback)`
|
||||
|
||||
Performs an LDAP modifyDN (rename) operation against an entry in the LDAP
|
||||
server. A couple points with this client API:
|
||||
|
||||
* There is no ability to set "keep old dn." It's always going to flag the old
|
||||
dn to be purged.
|
||||
* The client code will automatically figure out if the request is a "new
|
||||
superior" request ("new superior" means move to a different part of the tree,
|
||||
as opposed to just renaming the leaf).
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
client.modifyDN('cn=foo, o=example', 'cn=bar', (err) => {
|
||||
assert.ifError(err);
|
||||
});
|
||||
```
|
||||
|
||||
# search
|
||||
`search(base, options, controls, callback)`
|
||||
|
||||
Performs a search operation against the LDAP server.
|
||||
|
||||
The search operation is more complex than the other operations, so this one
|
||||
takes an `options` object for all the parameters. However, ldapjs makes some
|
||||
defaults for you so that if you pass nothing in, it's pretty much equivalent
|
||||
to an HTTP GET operation (i.e., base search against the DN, filter set to
|
||||
always match).
|
||||
|
||||
Like every other operation, `base` is a DN string.
|
||||
|
||||
Options can be a string representing a valid LDAP filter or an object
|
||||
containing the following fields:
|
||||
|
||||
|Attribute |Description |
|
||||
|-----------|---------------------------------------------------|
|
||||
|scope |One of `base`, `one`, or `sub`. Defaults to `base`.|
|
||||
|filter |A string version of an LDAP filter (see below), or a programatically constructed `Filter` object. Defaults to `(objectclass=*)`.|
|
||||
|attributes |attributes to select and return (if these are set, the server will return *only* these attributes). Defaults to the empty set, which means all attributes. You can provide a string if you want a single attribute or an array of string for one or many.|
|
||||
|attrsOnly |boolean on whether you want the server to only return the names of the attributes, and not their values. Borderline useless. Defaults to false.|
|
||||
|sizeLimit |the maximum number of entries to return. Defaults to 0 (unlimited).|
|
||||
|timeLimit |the maximum amount of time the server should take in responding, in seconds. Defaults to 10. Lots of servers will ignore this.|
|
||||
|paged |enable and/or configure automatic result paging|
|
||||
|
||||
Responses inside callback of the `search` method are an `EventEmitter` where you will get a notification for
|
||||
each `searchEntry` that comes back from the server. You will additionally be able to listen for a `searchRequest`
|
||||
, `searchReference`, `error` and `end` event.
|
||||
`searchRequest` is emitted immediately after every `SearchRequest` is sent with a `SearchRequest` parameter. You can do operations
|
||||
like `client.abandon` with `searchRequest.messageId` to abandon this search request. Note that the `error` event will
|
||||
only be for client/TCP errors, not LDAP error codes like the other APIs. You'll want to check the LDAP status code
|
||||
(likely for `0`) on the `end` event to assert success. LDAP search results can give you a lot of status codes, such as
|
||||
time or size exceeded, busy, inappropriate matching, etc., which is why this method doesn't try to wrap up the code
|
||||
matching.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
const opts = {
|
||||
filter: '(&(l=Seattle)(email=*@foo.com))',
|
||||
scope: 'sub',
|
||||
attributes: ['dn', 'sn', 'cn']
|
||||
};
|
||||
|
||||
client.search('o=example', opts, (err, res) => {
|
||||
assert.ifError(err);
|
||||
|
||||
res.on('searchRequest', (searchRequest) => {
|
||||
console.log('searchRequest: ', searchRequest.messageId);
|
||||
});
|
||||
res.on('searchEntry', (entry) => {
|
||||
console.log('entry: ' + JSON.stringify(entry.pojo));
|
||||
});
|
||||
res.on('searchReference', (referral) => {
|
||||
console.log('referral: ' + referral.uris.join());
|
||||
});
|
||||
res.on('error', (err) => {
|
||||
console.error('error: ' + err.message);
|
||||
});
|
||||
res.on('end', (result) => {
|
||||
console.log('status: ' + result.status);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Filter Strings
|
||||
|
||||
The easiest way to write search filters is to write them compliant with RFC2254,
|
||||
which is "The string representation of LDAP search filters." Note that
|
||||
ldapjs doesn't support extensible matching, since it's one of those features
|
||||
that almost nobody actually uses in practice.
|
||||
|
||||
Assuming you don't really want to read the RFC, search filters in LDAP are
|
||||
basically are a "tree" of attribute/value assertions, with the tree specified
|
||||
in prefix notation. For example, let's start simple, and build up a complicated
|
||||
filter. The most basic filter is equality, so let's assume you want to search
|
||||
for an attribute `email` with a value of `foo@bar.com`. The syntax would be:
|
||||
|
||||
```
|
||||
(email=foo@bar.com)
|
||||
```
|
||||
|
||||
ldapjs requires all filters to be surrounded by '()' blocks. Ok, that was easy.
|
||||
Let's now assume that you want to find all records where the email is actually
|
||||
just anything in the "@bar.com" domain and the location attribute is set to
|
||||
Seattle:
|
||||
|
||||
```
|
||||
(&(email=*@bar.com)(l=Seattle))
|
||||
```
|
||||
|
||||
Now our filter is actually three LDAP filters. We have an `and` filter (single
|
||||
amp `&`), an `equality` filter `(the l=Seattle)`, and a `substring` filter.
|
||||
Substrings are wildcard filters. They use `*` as the wildcard. You can put more
|
||||
than one wildcard for a given string. For example you could do `(email=*@*bar.com)`
|
||||
to match any email of @bar.com or its subdomains like `"example@foo.bar.com"`.
|
||||
|
||||
Now, let's say we also want to set our filter to include a
|
||||
specification that either the employeeType *not* be a manager nor a secretary:
|
||||
|
||||
```
|
||||
(&(email=*@bar.com)(l=Seattle)(!(|(employeeType=manager)(employeeType=secretary))))
|
||||
```
|
||||
|
||||
The `not` character is represented as a `!`, the `or` as a single pipe `|`.
|
||||
It gets a little bit complicated, but it's actually quite powerful, and lets you
|
||||
find almost anything you're looking for.
|
||||
|
||||
## Paging
|
||||
Many LDAP server enforce size limits upon the returned result set (commonly
|
||||
1000). In order to retrieve results beyond this limit, a `PagedResultControl`
|
||||
is passed between the client and server to iterate through the entire dataset.
|
||||
While callers could choose to do this manually via the `controls` parameter to
|
||||
`search()`, ldapjs has internal mechanisms to easily automate the process. The
|
||||
most simple way to use the paging automation is to set the `paged` option to
|
||||
true when performing a search:
|
||||
|
||||
```js
|
||||
const opts = {
|
||||
filter: '(objectclass=commonobject)',
|
||||
scope: 'sub',
|
||||
paged: true,
|
||||
sizeLimit: 200
|
||||
};
|
||||
client.search('o=largedir', opts, (err, res) => {
|
||||
assert.ifError(err);
|
||||
res.on('searchEntry', (entry) => {
|
||||
// do per-entry processing
|
||||
});
|
||||
res.on('page', (result) => {
|
||||
console.log('page end');
|
||||
});
|
||||
res.on('error', (resErr) => {
|
||||
assert.ifError(resErr);
|
||||
});
|
||||
res.on('end', (result) => {
|
||||
console.log('done ');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
This will enable paging with a default page size of 199 (`sizeLimit` - 1) and
|
||||
will output all of the resulting objects via the `searchEntry` event. At the
|
||||
end of each result during the operation, a `page` event will be emitted as
|
||||
well (which includes the intermediate `searchResult` object).
|
||||
|
||||
For those wanting more precise control over the process, an object with several
|
||||
parameters can be provided for the `paged` option. The `pageSize` parameter
|
||||
sets the size of result pages requested from the server. If no value is
|
||||
specified, it will fall back to the default (100 or `sizeLimit` - 1, to obey
|
||||
the RFC). The `pagePause` parameter allows back-pressure to be exerted on the
|
||||
paged search operation by pausing at the end of each page. When enabled, a
|
||||
callback function is passed as an additional parameter to `page` events. The
|
||||
client will wait to request the next page until that callback is executed.
|
||||
|
||||
Here is an example where both of those parameters are used:
|
||||
|
||||
```js
|
||||
const queue = new MyWorkQueue(someSlowWorkFunction);
|
||||
const opts = {
|
||||
filter: '(objectclass=commonobject)',
|
||||
scope: 'sub',
|
||||
paged: {
|
||||
pageSize: 250,
|
||||
pagePause: true
|
||||
},
|
||||
};
|
||||
client.search('o=largerdir', opts, (err, res) => {
|
||||
assert.ifError(err);
|
||||
res.on('searchEntry', (entry) => {
|
||||
// Submit incoming objects to queue
|
||||
queue.push(entry);
|
||||
});
|
||||
res.on('page', (result, cb) => {
|
||||
// Allow the queue to flush before fetching next page
|
||||
queue.cbWhenFlushed(cb);
|
||||
});
|
||||
res.on('error', (resErr) => {
|
||||
assert.ifError(resErr);
|
||||
});
|
||||
res.on('end', (result) => {
|
||||
console.log('done');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
# starttls
|
||||
`starttls(options, controls, callback)`
|
||||
|
||||
Attempt to secure existing LDAP connection via STARTTLS.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
const opts = {
|
||||
ca: [fs.readFileSync('mycacert.pem')]
|
||||
};
|
||||
|
||||
client.starttls(opts, (err, res) => {
|
||||
assert.ifError(err);
|
||||
|
||||
// Client communication now TLS protected
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
# unbind
|
||||
`unbind(callback)`
|
||||
|
||||
Performs an unbind operation against the LDAP server.
|
||||
|
||||
Note that unbind operation is not an opposite operation
|
||||
for bind. Unbinding results in disconnecting the client
|
||||
regardless of whether a bind operation was performed.
|
||||
|
||||
The `callback` argument is optional as unbind does
|
||||
not have a response.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
client.unbind((err) => {
|
||||
assert.ifError(err);
|
||||
});
|
||||
```
|
||||
127
node_modules/ldapjs/docs/dn.md
generated
vendored
Normal file
127
node_modules/ldapjs/docs/dn.md
generated
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
---
|
||||
title: DN API | ldapjs
|
||||
---
|
||||
|
||||
# ldapjs DN API
|
||||
|
||||
<div class="intro">
|
||||
|
||||
This document covers the ldapjs DN API and assumes that you are familiar
|
||||
with LDAP. If you're not, read the [guide](guide.html) first.
|
||||
|
||||
</div>
|
||||
|
||||
DNs are LDAP distinguished names, and are composed of a set of RDNs (relative
|
||||
distinguished names). [RFC2253](http://www.ietf.org/rfc/rfc2253.txt) has the
|
||||
complete specification, but basically an RDN is an attribute value assertion
|
||||
with `=` as the seperator, like: `cn=foo` where 'cn' is 'commonName' and 'foo'
|
||||
is the value. You can have compound RDNs by using the `+` character:
|
||||
`cn=foo+sn=bar`. As stated above, DNs are a set of RDNs, typically separated
|
||||
with the `,` character, like: `cn=foo, ou=people, o=example`. This uniquely
|
||||
identifies an entry in the tree, and is read "bottom up".
|
||||
|
||||
# parseDN(dnString)
|
||||
|
||||
The `parseDN` API converts a string representation of a DN into an ldapjs DN
|
||||
object; in most cases this will be handled for you under the covers of the
|
||||
ldapjs framework, but if you need it, it's there.
|
||||
|
||||
```js
|
||||
const parseDN = require('ldapjs').parseDN;
|
||||
|
||||
const dn = parseDN('cn=foo+sn=bar, ou=people, o=example');
|
||||
console.log(dn.toString());
|
||||
```
|
||||
|
||||
# DN
|
||||
|
||||
The DN object is largely what you'll be interacting with, since all the server
|
||||
APIs are setup to give you a DN object.
|
||||
|
||||
## childOf(dn)
|
||||
|
||||
Returns a boolean indicating whether 'this' is a child of the passed in dn. The
|
||||
`dn` argument can be either a string or a DN.
|
||||
|
||||
```js
|
||||
server.add('o=example', (req, res, next) => {
|
||||
if (req.dn.childOf('ou=people, o=example')) {
|
||||
...
|
||||
} else {
|
||||
...
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## parentOf(dn)
|
||||
|
||||
The inverse of `childOf`; returns a boolean on whether or not `this` is a parent
|
||||
of the passed in dn. Like `childOf`, can take either a string or a DN.
|
||||
|
||||
```js
|
||||
server.add('o=example', (req, res, next) => {
|
||||
const dn = parseDN('ou=people, o=example');
|
||||
if (dn.parentOf(req.dn)) {
|
||||
...
|
||||
} else {
|
||||
...
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## equals(dn)
|
||||
|
||||
Returns a boolean indicating whether `this` is equivalent to the passed in `dn`
|
||||
argument. `dn` can be a string or a DN.
|
||||
|
||||
```js
|
||||
server.add('o=example', (req, res, next) => {
|
||||
if (req.dn.equals('cn=foo, ou=people, o=example')) {
|
||||
...
|
||||
} else {
|
||||
...
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## parent()
|
||||
|
||||
Returns a DN object that is the direct parent of `this`. If there is no parent
|
||||
this can return `null` (e.g. `parseDN('o=example').parent()` will return null).
|
||||
|
||||
|
||||
## format(options)
|
||||
|
||||
Convert a DN object to string according to specified formatting options. These
|
||||
options are divided into two types. Preservation Options use data recorded
|
||||
during parsing to preserve details of the original DN. Modification options
|
||||
alter string formatting defaults. Preservation options _always_ take
|
||||
precedence over Modification Options.
|
||||
|
||||
Preservation Options:
|
||||
|
||||
- `keepOrder`: Order of multi-value RDNs.
|
||||
- `keepQuote`: RDN values which were quoted will remain so.
|
||||
- `keepSpace`: Leading/trailing spaces will be output.
|
||||
- `keepCase`: Parsed attribute name will be output instead of lowercased version.
|
||||
|
||||
Modification Options:
|
||||
|
||||
- `upperName`: RDN names will be uppercased instead of lowercased.
|
||||
- `skipSpace`: Disable trailing space after RDN separators
|
||||
|
||||
## setFormat(options)
|
||||
|
||||
Sets the default `options` for string formatting when `toString` is called.
|
||||
It accepts the same parameters as `format`.
|
||||
|
||||
|
||||
## toString()
|
||||
|
||||
Returns the string representation of `this`.
|
||||
|
||||
```js
|
||||
server.add('o=example', (req, res, next) => {
|
||||
console.log(req.dn.toString());
|
||||
});
|
||||
```
|
||||
94
node_modules/ldapjs/docs/errors.md
generated
vendored
Normal file
94
node_modules/ldapjs/docs/errors.md
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
---
|
||||
title: Errors API | ldapjs
|
||||
---
|
||||
|
||||
# ldapjs Errors API
|
||||
|
||||
<div class="intro">
|
||||
|
||||
This document covers the ldapjs errors API and assumes that you are familiar
|
||||
with LDAP. If you're not, read the [guide](guide.html) first.
|
||||
|
||||
</div>
|
||||
|
||||
All errors in the ldapjs framework extend from an abstract error type called
|
||||
`LDAPError`. In addition to the properties listed below, all errors will have
|
||||
a `stack` property correctly set.
|
||||
|
||||
In general, you'll be using the errors in ldapjs like:
|
||||
|
||||
```js
|
||||
const ldap = require('ldapjs');
|
||||
|
||||
const db = {};
|
||||
|
||||
server.add('o=example', (req, res, next) => {
|
||||
const parent = req.dn.parent();
|
||||
if (parent) {
|
||||
if (!db[parent.toString()])
|
||||
return next(new ldap.NoSuchObjectError(parent.toString()));
|
||||
}
|
||||
if (db[req.dn.toString()])
|
||||
return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
|
||||
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
I.e., if you just pass them into the `next()` handler, ldapjs will automatically
|
||||
return the appropriate LDAP error message, and stop the handler chain.
|
||||
|
||||
All errors will have the following properties:
|
||||
|
||||
## code
|
||||
|
||||
Returns the LDAP status code associated with this error.
|
||||
|
||||
## name
|
||||
|
||||
The name of this error.
|
||||
|
||||
## message
|
||||
|
||||
The message that will be returned to the client.
|
||||
|
||||
# Complete list of LDAPError subclasses
|
||||
|
||||
* OperationsError
|
||||
* ProtocolError
|
||||
* TimeLimitExceededError
|
||||
* SizeLimitExceededError
|
||||
* CompareFalseError
|
||||
* CompareTrueError
|
||||
* AuthMethodNotSupportedError
|
||||
* StrongAuthRequiredError
|
||||
* ReferralError
|
||||
* AdminLimitExceededError
|
||||
* UnavailableCriticalExtensionError
|
||||
* ConfidentialityRequiredError
|
||||
* SaslBindInProgressError
|
||||
* NoSuchAttributeError
|
||||
* UndefinedAttributeTypeError
|
||||
* InappropriateMatchingError
|
||||
* ConstraintViolationError
|
||||
* AttributeOrValueExistsError
|
||||
* InvalidAttriubteSyntaxError
|
||||
* NoSuchObjectError
|
||||
* AliasProblemError
|
||||
* InvalidDnSyntaxError
|
||||
* AliasDerefProblemError
|
||||
* InappropriateAuthenticationError
|
||||
* InvalidCredentialsError
|
||||
* InsufficientAccessRightsError
|
||||
* BusyError
|
||||
* UnavailableError
|
||||
* UnwillingToPerformError
|
||||
* LoopDetectError
|
||||
* NamingViolationError
|
||||
* ObjectclassViolationError
|
||||
* NotAllowedOnNonLeafError
|
||||
* NotAllowedOnRdnError
|
||||
* EntryAlreadyExistsError
|
||||
* ObjectclassModsProhibitedError
|
||||
* AffectsMultipleDsasError
|
||||
* OtherError
|
||||
625
node_modules/ldapjs/docs/examples.md
generated
vendored
Normal file
625
node_modules/ldapjs/docs/examples.md
generated
vendored
Normal file
@ -0,0 +1,625 @@
|
||||
---
|
||||
title: Examples | ldapjs
|
||||
---
|
||||
|
||||
# ldapjs Examples
|
||||
|
||||
<div class="intro">
|
||||
|
||||
This page contains a (hopefully) growing list of sample code to get you started
|
||||
with ldapjs.
|
||||
|
||||
</div>
|
||||
|
||||
# In-memory server
|
||||
|
||||
```js
|
||||
const ldap = require('ldapjs');
|
||||
|
||||
|
||||
///--- Shared handlers
|
||||
|
||||
function authorize(req, res, next) {
|
||||
/* Any user may search after bind, only cn=root has full power */
|
||||
const isSearch = (req instanceof ldap.SearchRequest);
|
||||
if (!req.connection.ldap.bindDN.equals('cn=root') && !isSearch)
|
||||
return next(new ldap.InsufficientAccessRightsError());
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
///--- Globals
|
||||
|
||||
const SUFFIX = 'o=joyent';
|
||||
const db = {};
|
||||
const server = ldap.createServer();
|
||||
|
||||
|
||||
|
||||
server.bind('cn=root', (req, res, next) => {
|
||||
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
|
||||
return next(new ldap.InvalidCredentialsError());
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
server.add(SUFFIX, authorize, (req, res, next) => {
|
||||
const dn = req.dn.toString();
|
||||
|
||||
if (db[dn])
|
||||
return next(new ldap.EntryAlreadyExistsError(dn));
|
||||
|
||||
db[dn] = req.toObject().attributes;
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
server.bind(SUFFIX, (req, res, next) => {
|
||||
const dn = req.dn.toString();
|
||||
if (!db[dn])
|
||||
return next(new ldap.NoSuchObjectError(dn));
|
||||
|
||||
if (!db[dn].userpassword)
|
||||
return next(new ldap.NoSuchAttributeError('userPassword'));
|
||||
|
||||
if (db[dn].userpassword.indexOf(req.credentials) === -1)
|
||||
return next(new ldap.InvalidCredentialsError());
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
server.compare(SUFFIX, authorize, (req, res, next) => {
|
||||
const dn = req.dn.toString();
|
||||
if (!db[dn])
|
||||
return next(new ldap.NoSuchObjectError(dn));
|
||||
|
||||
if (!db[dn][req.attribute])
|
||||
return next(new ldap.NoSuchAttributeError(req.attribute));
|
||||
|
||||
const matches = false;
|
||||
const vals = db[dn][req.attribute];
|
||||
for (const value of vals) {
|
||||
if (value === req.value) {
|
||||
matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
res.end(matches);
|
||||
return next();
|
||||
});
|
||||
|
||||
server.del(SUFFIX, authorize, (req, res, next) => {
|
||||
const dn = req.dn.toString();
|
||||
if (!db[dn])
|
||||
return next(new ldap.NoSuchObjectError(dn));
|
||||
|
||||
delete db[dn];
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
server.modify(SUFFIX, authorize, (req, res, next) => {
|
||||
const dn = req.dn.toString();
|
||||
if (!req.changes.length)
|
||||
return next(new ldap.ProtocolError('changes required'));
|
||||
if (!db[dn])
|
||||
return next(new ldap.NoSuchObjectError(dn));
|
||||
|
||||
const entry = db[dn];
|
||||
|
||||
for (const change of req.changes) {
|
||||
mod = change.modification;
|
||||
switch (change.operation) {
|
||||
case 'replace':
|
||||
if (!entry[mod.type])
|
||||
return next(new ldap.NoSuchAttributeError(mod.type));
|
||||
|
||||
if (!mod.vals || !mod.vals.length) {
|
||||
delete entry[mod.type];
|
||||
} else {
|
||||
entry[mod.type] = mod.vals;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'add':
|
||||
if (!entry[mod.type]) {
|
||||
entry[mod.type] = mod.vals;
|
||||
} else {
|
||||
for (const v of mod.vals) {
|
||||
if (entry[mod.type].indexOf(v) === -1)
|
||||
entry[mod.type].push(v);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
if (!entry[mod.type])
|
||||
return next(new ldap.NoSuchAttributeError(mod.type));
|
||||
|
||||
delete entry[mod.type];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
server.search(SUFFIX, authorize, (req, res, next) => {
|
||||
const dn = req.dn.toString();
|
||||
if (!db[dn])
|
||||
return next(new ldap.NoSuchObjectError(dn));
|
||||
|
||||
let scopeCheck;
|
||||
|
||||
switch (req.scope) {
|
||||
case 'base':
|
||||
if (req.filter.matches(db[dn])) {
|
||||
res.send({
|
||||
dn: dn,
|
||||
attributes: db[dn]
|
||||
});
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
|
||||
case 'one':
|
||||
scopeCheck = (k) => {
|
||||
if (req.dn.equals(k))
|
||||
return true;
|
||||
|
||||
const parent = ldap.parseDN(k).parent();
|
||||
return (parent ? parent.equals(req.dn) : false);
|
||||
};
|
||||
break;
|
||||
|
||||
case 'sub':
|
||||
scopeCheck = (k) => {
|
||||
return (req.dn.equals(k) || req.dn.parentOf(k));
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
const keys = Object.keys(db);
|
||||
for (const key of keys) {
|
||||
if (!scopeCheck(key))
|
||||
return;
|
||||
|
||||
if (req.filter.matches(db[key])) {
|
||||
res.send({
|
||||
dn: key,
|
||||
attributes: db[key]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
|
||||
|
||||
///--- Fire it up
|
||||
|
||||
server.listen(1389, () => {
|
||||
console.log('LDAP server up at: %s', server.url);
|
||||
});
|
||||
```
|
||||
|
||||
# /etc/passwd server
|
||||
|
||||
```js
|
||||
const fs = require('fs');
|
||||
const ldap = require('ldapjs');
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
|
||||
|
||||
///--- Shared handlers
|
||||
|
||||
function authorize(req, res, next) {
|
||||
if (!req.connection.ldap.bindDN.equals('cn=root'))
|
||||
return next(new ldap.InsufficientAccessRightsError());
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
function loadPasswdFile(req, res, next) {
|
||||
fs.readFile('/etc/passwd', 'utf8', (err, data) => {
|
||||
if (err)
|
||||
return next(new ldap.OperationsError(err.message));
|
||||
|
||||
req.users = {};
|
||||
|
||||
const lines = data.split('\n');
|
||||
for (const line of lines) {
|
||||
if (!line || /^#/.test(line))
|
||||
continue;
|
||||
|
||||
const record = line.split(':');
|
||||
if (!record || !record.length)
|
||||
continue;
|
||||
|
||||
req.users[record[0]] = {
|
||||
dn: 'cn=' + record[0] + ', ou=users, o=myhost',
|
||||
attributes: {
|
||||
cn: record[0],
|
||||
uid: record[2],
|
||||
gid: record[3],
|
||||
description: record[4],
|
||||
homedirectory: record[5],
|
||||
shell: record[6] || '',
|
||||
objectclass: 'unixUser'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const pre = [authorize, loadPasswdFile];
|
||||
|
||||
|
||||
|
||||
///--- Mainline
|
||||
|
||||
const server = ldap.createServer();
|
||||
|
||||
server.bind('cn=root', (req, res, next) => {
|
||||
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
|
||||
return next(new ldap.InvalidCredentialsError());
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
|
||||
server.add('ou=users, o=myhost', pre, (req, res, next) => {
|
||||
if (!req.dn.rdns[0].cn)
|
||||
return next(new ldap.ConstraintViolationError('cn required'));
|
||||
|
||||
if (req.users[req.dn.rdns[0].cn])
|
||||
return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
|
||||
|
||||
const entry = req.toObject().attributes;
|
||||
|
||||
if (entry.objectclass.indexOf('unixUser') === -1)
|
||||
return next(new ldap.ConstraintViolationError('entry must be a unixUser'));
|
||||
|
||||
const opts = ['-m'];
|
||||
if (entry.description) {
|
||||
opts.push('-c');
|
||||
opts.push(entry.description[0]);
|
||||
}
|
||||
if (entry.homedirectory) {
|
||||
opts.push('-d');
|
||||
opts.push(entry.homedirectory[0]);
|
||||
}
|
||||
if (entry.gid) {
|
||||
opts.push('-g');
|
||||
opts.push(entry.gid[0]);
|
||||
}
|
||||
if (entry.shell) {
|
||||
opts.push('-s');
|
||||
opts.push(entry.shell[0]);
|
||||
}
|
||||
if (entry.uid) {
|
||||
opts.push('-u');
|
||||
opts.push(entry.uid[0]);
|
||||
}
|
||||
opts.push(entry.cn[0]);
|
||||
const useradd = spawn('useradd', opts);
|
||||
|
||||
const messages = [];
|
||||
|
||||
useradd.stdout.on('data', (data) => {
|
||||
messages.push(data.toString());
|
||||
});
|
||||
useradd.stderr.on('data', (data) => {
|
||||
messages.push(data.toString());
|
||||
});
|
||||
|
||||
useradd.on('exit', (code) => {
|
||||
if (code !== 0) {
|
||||
let msg = '' + code;
|
||||
if (messages.length)
|
||||
msg += ': ' + messages.join();
|
||||
return next(new ldap.OperationsError(msg));
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
server.modify('ou=users, o=myhost', pre, (req, res, next) => {
|
||||
if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn])
|
||||
return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
if (!req.changes.length)
|
||||
return next(new ldap.ProtocolError('changes required'));
|
||||
|
||||
const user = req.users[req.dn.rdns[0].cn].attributes;
|
||||
let mod;
|
||||
|
||||
for (const change of req.changes) {
|
||||
mod = change.modification;
|
||||
switch (change.operation) {
|
||||
case 'replace':
|
||||
if (mod.type !== 'userpassword' || !mod.vals || !mod.vals.length)
|
||||
return next(new ldap.UnwillingToPerformError('only password updates ' +
|
||||
'allowed'));
|
||||
break;
|
||||
case 'add':
|
||||
case 'delete':
|
||||
return next(new ldap.UnwillingToPerformError('only replace allowed'));
|
||||
}
|
||||
}
|
||||
|
||||
const passwd = spawn('chpasswd', ['-c', 'MD5']);
|
||||
passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8');
|
||||
|
||||
passwd.on('exit', (code) => {
|
||||
if (code !== 0)
|
||||
return next(new ldap.OperationsError('' + code));
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
server.del('ou=users, o=myhost', pre, (req, res, next) => {
|
||||
if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn])
|
||||
return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
const userdel = spawn('userdel', ['-f', req.dn.rdns[0].cn]);
|
||||
|
||||
const messages = [];
|
||||
userdel.stdout.on('data', (data) => {
|
||||
messages.push(data.toString());
|
||||
});
|
||||
userdel.stderr.on('data', (data) => {
|
||||
messages.push(data.toString());
|
||||
});
|
||||
|
||||
userdel.on('exit', (code) => {
|
||||
if (code !== 0) {
|
||||
let msg = '' + code;
|
||||
if (messages.length)
|
||||
msg += ': ' + messages.join();
|
||||
return next(new ldap.OperationsError(msg));
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
server.search('o=myhost', pre, (req, res, next) => {
|
||||
const keys = Object.keys(req.users);
|
||||
for (const k of keys) {
|
||||
if (req.filter.matches(req.users[k].attributes))
|
||||
res.send(req.users[k]);
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
|
||||
|
||||
// LDAP "standard" listens on 389, but whatever.
|
||||
server.listen(1389, '127.0.0.1', () => {
|
||||
console.log('/etc/passwd LDAP server up at: %s', server.url);
|
||||
});
|
||||
```
|
||||
|
||||
# Address Book
|
||||
|
||||
This example is courtesy of [Diogo Resende](https://github.com/dresende) and
|
||||
illustrates setting up an address book for typical mail clients such as
|
||||
Thunderbird or Evolution over a MySQL database.
|
||||
|
||||
```js
|
||||
// MySQL test: (create on database 'abook' with username 'abook' and password 'abook')
|
||||
//
|
||||
// CREATE TABLE IF NOT EXISTS `users` (
|
||||
// `id` int(5) unsigned NOT NULL AUTO_INCREMENT,
|
||||
// `username` varchar(50) NOT NULL,
|
||||
// `password` varchar(50) NOT NULL,
|
||||
// PRIMARY KEY (`id`),
|
||||
// KEY `username` (`username`)
|
||||
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
// INSERT INTO `users` (`username`, `password`) VALUES
|
||||
// ('demo', 'demo');
|
||||
// CREATE TABLE IF NOT EXISTS `contacts` (
|
||||
// `id` int(5) unsigned NOT NULL AUTO_INCREMENT,
|
||||
// `user_id` int(5) unsigned NOT NULL,
|
||||
// `name` varchar(100) NOT NULL,
|
||||
// `email` varchar(255) NOT NULL,
|
||||
// PRIMARY KEY (`id`),
|
||||
// KEY `user_id` (`user_id`)
|
||||
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
// INSERT INTO `contacts` (`user_id`, `name`, `email`) VALUES
|
||||
// (1, 'John Doe', 'john.doe@example.com'),
|
||||
// (1, 'Jane Doe', 'jane.doe@example.com');
|
||||
//
|
||||
|
||||
const ldap = require('ldapjs');
|
||||
const mysql = require("mysql");
|
||||
const server = ldap.createServer();
|
||||
const addrbooks = {};
|
||||
const userinfo = {};
|
||||
const ldap_port = 389;
|
||||
const basedn = "dc=example, dc=com";
|
||||
const company = "Example";
|
||||
const db = mysql.createClient({
|
||||
user: "abook",
|
||||
password: "abook",
|
||||
database: "abook"
|
||||
});
|
||||
|
||||
db.query("SELECT c.*,u.username,u.password " +
|
||||
"FROM contacts c JOIN users u ON c.user_id=u.id",
|
||||
(err, contacts) => {
|
||||
if (err) {
|
||||
console.log("Error fetching contacts", err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
for (const contact of contacts) {
|
||||
if (!addrbooks.hasOwnProperty(contact.username)) {
|
||||
addrbooks[contact.username] = [];
|
||||
userinfo["cn=" + contact.username + ", " + basedn] = {
|
||||
abook: addrbooks[contact.username],
|
||||
pwd: contact.password
|
||||
};
|
||||
}
|
||||
|
||||
const p = contact.name.indexOf(" ");
|
||||
if (p != -1)
|
||||
contact.firstname = contact.name.substr(0, p);
|
||||
|
||||
p = contact.name.lastIndexOf(" ");
|
||||
if (p != -1)
|
||||
contact.surname = contact.name.substr(p + 1);
|
||||
|
||||
addrbooks[contact.username].push({
|
||||
dn: "cn=" + contact.name + ", " + basedn,
|
||||
attributes: {
|
||||
objectclass: [ "top" ],
|
||||
cn: contact.name,
|
||||
mail: contact.email,
|
||||
givenname: contact.firstname,
|
||||
sn: contact.surname,
|
||||
ou: company
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
server.bind(basedn, (req, res, next) => {
|
||||
const username = req.dn.toString();
|
||||
const password = req.credentials;
|
||||
|
||||
if (!userinfo.hasOwnProperty(username) ||
|
||||
userinfo[username].pwd != password) {
|
||||
return next(new ldap.InvalidCredentialsError());
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
|
||||
server.search(basedn, (req, res, next) => {
|
||||
const binddn = req.connection.ldap.bindDN.toString();
|
||||
|
||||
if (userinfo.hasOwnProperty(binddn)) {
|
||||
for (const abook of userinfo[binddn].abook) {
|
||||
if (req.filter.matches(abook.attributes))
|
||||
res.send(abook);
|
||||
}
|
||||
}
|
||||
res.end();
|
||||
});
|
||||
|
||||
server.listen(ldap_port, () => {
|
||||
console.log("Addressbook started at %s", server.url);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
To test out this example, try:
|
||||
|
||||
```shell
|
||||
$ ldapsearch -H ldap://localhost:389 -x -D cn=demo,dc=example,dc=com \
|
||||
-w demo -b "dc=example,dc=com" objectclass=*
|
||||
```
|
||||
|
||||
# Multi-threaded Server
|
||||
|
||||
This example demonstrates multi-threading via the `cluster` module utilizing a `net` server for initial socket receipt. An alternate example demonstrating use of the `connectionRouter` `serverOptions` hook is available in the `examples` directory.
|
||||
|
||||
```js
|
||||
const cluster = require('cluster');
|
||||
const ldap = require('ldapjs');
|
||||
const net = require('net');
|
||||
const os = require('os');
|
||||
|
||||
const threads = [];
|
||||
threads.getNext = function () {
|
||||
return (Math.floor(Math.random() * this.length));
|
||||
};
|
||||
|
||||
const serverOptions = {
|
||||
port: 1389
|
||||
};
|
||||
|
||||
if (cluster.isMaster) {
|
||||
const server = net.createServer(serverOptions, (socket) => {
|
||||
socket.pause();
|
||||
console.log('ldapjs client requesting connection');
|
||||
let routeTo = threads.getNext();
|
||||
threads[routeTo].send({ type: 'connection' }, socket);
|
||||
});
|
||||
|
||||
for (let i = 0; i < os.cpus().length; i++) {
|
||||
let thread = cluster.fork({
|
||||
'id': i
|
||||
});
|
||||
thread.id = i;
|
||||
thread.on('message', function (msg) {
|
||||
|
||||
});
|
||||
threads.push(thread);
|
||||
}
|
||||
|
||||
server.listen(serverOptions.port, function () {
|
||||
console.log('ldapjs listening at ldap://127.0.0.1:' + serverOptions.port);
|
||||
});
|
||||
} else {
|
||||
const server = ldap.createServer(serverOptions);
|
||||
|
||||
let threadId = process.env.id;
|
||||
|
||||
process.on('message', (msg, socket) => {
|
||||
switch (msg.type) {
|
||||
case 'connection':
|
||||
server.newConnection(socket);
|
||||
socket.resume();
|
||||
console.log('ldapjs client connection accepted on ' + threadId.toString());
|
||||
}
|
||||
});
|
||||
|
||||
server.search('dc=example', function (req, res, next) {
|
||||
console.log('ldapjs search initiated on ' + threadId.toString());
|
||||
var obj = {
|
||||
dn: req.dn.toString(),
|
||||
attributes: {
|
||||
objectclass: ['organization', 'top'],
|
||||
o: 'example'
|
||||
}
|
||||
};
|
||||
|
||||
if (req.filter.matches(obj.attributes))
|
||||
res.send(obj);
|
||||
|
||||
res.end();
|
||||
});
|
||||
}
|
||||
```
|
||||
317
node_modules/ldapjs/docs/filters.md
generated
vendored
Normal file
317
node_modules/ldapjs/docs/filters.md
generated
vendored
Normal file
@ -0,0 +1,317 @@
|
||||
---
|
||||
title: Filters API | ldapjs
|
||||
---
|
||||
|
||||
# ldapjs Filters API
|
||||
|
||||
<div class="intro">
|
||||
|
||||
This document covers the ldapjs filters API and assumes that you are familiar
|
||||
with LDAP. If you're not, read the [guide](guide.html) first.
|
||||
|
||||
</div>
|
||||
|
||||
LDAP search filters are really the backbone of LDAP search operations, and
|
||||
ldapjs tries to get you in "easy" with them if your dataset is small, and also
|
||||
lets you introspect them if you want to write a "query planner". For reference,
|
||||
make sure to read over [RFC2254](http://www.ietf.org/rfc/rfc2254.txt), as this
|
||||
explains the LDAPv3 text filter representation.
|
||||
|
||||
ldapjs gives you a distinct object type mapping to each filter that is
|
||||
context-sensitive. However, _all_ filters have a `matches()` method on them, if
|
||||
that's all you need. Most filters will have an `attribute` property on them,
|
||||
since "simple" filters all operate on an attribute/value assertion. The
|
||||
"complex" filters are really aggregations of other filters (i.e. 'and'), and so
|
||||
these don't provide that property.
|
||||
|
||||
All Filters in the ldapjs framework extend from `Filter`, which wil have the
|
||||
property `type` available; this will return a string name for the filter, and
|
||||
will be one of:
|
||||
|
||||
# parseFilter(filterString)
|
||||
|
||||
Parses an [RFC2254](http://www.ietf.org/rfc/rfc2254.txt) filter string into an
|
||||
ldapjs object(s). If the filter is "complex", it will be a "tree" of objects.
|
||||
For example:
|
||||
|
||||
```js
|
||||
const parseFilter = require('ldapjs').parseFilter;
|
||||
|
||||
const f = parseFilter('(objectclass=*)');
|
||||
```
|
||||
|
||||
Is a "simple" filter, and would just return a `PresenceFilter` object. However,
|
||||
|
||||
```js
|
||||
const f = parseFilter('(&(employeeType=manager)(l=Seattle))');
|
||||
```
|
||||
|
||||
Would return an `AndFilter`, which would have a `filters` array of two
|
||||
`EqualityFilter` objects.
|
||||
|
||||
`parseFilter` will throw if an invalid string is passed in (that is, a
|
||||
syntactically invalid string).
|
||||
|
||||
# EqualityFilter
|
||||
|
||||
The equality filter is used to check exact matching of attribute/value
|
||||
assertions. This object will have an `attribute` and `value` property, and the
|
||||
`name` property will be `equal`.
|
||||
|
||||
The string syntax for an equality filter is `(attr=value)`.
|
||||
|
||||
The `matches()` method will return true IFF the passed in object has a
|
||||
key matching `attribute` and a value matching `value`.
|
||||
|
||||
```js
|
||||
const f = new EqualityFilter({
|
||||
attribute: 'cn',
|
||||
value: 'foo'
|
||||
});
|
||||
|
||||
f.matches({cn: 'foo'}); => true
|
||||
f.matches({cn: 'bar'}); => false
|
||||
```
|
||||
|
||||
Equality matching uses "strict" type JavaScript comparison, and by default
|
||||
everything in ldapjs (and LDAP) is a UTF-8 string. If you want comparison
|
||||
of numbers, or something else, you'll need to use a middleware interceptor
|
||||
that transforms values of objects.
|
||||
|
||||
# PresenceFilter
|
||||
|
||||
The presence filter is used to check if an object has an attribute at all, with
|
||||
any value. This object will have an `attribute` property, and the `name`
|
||||
property will be `present`.
|
||||
|
||||
The string syntax for a presence filter is `(attr=*)`.
|
||||
|
||||
The `matches()` method will return true IFF the passed in object has a
|
||||
key matching `attribute`.
|
||||
|
||||
```js
|
||||
const f = new PresenceFilter({
|
||||
attribute: 'cn'
|
||||
});
|
||||
|
||||
f.matches({cn: 'foo'}); => true
|
||||
f.matches({sn: 'foo'}); => false
|
||||
```
|
||||
|
||||
# SubstringFilter
|
||||
|
||||
The substring filter is used to do wildcard matching of a string value. This
|
||||
object will have an `attribute` property and then it will have an `initial`
|
||||
property, which is the prefix match, an `any` which will be an array of strings
|
||||
that are to be found _somewhere_ in the target string, and a `final` property,
|
||||
which will be the suffix match of the string. `any` and `final` are both
|
||||
optional. The `name` property will be `substring`.
|
||||
|
||||
The string syntax for a presence filter is `(attr=foo*bar*cat*dog)`, which would
|
||||
map to:
|
||||
|
||||
```js
|
||||
{
|
||||
initial: 'foo',
|
||||
any: ['bar', 'cat'],
|
||||
final: 'dog'
|
||||
}
|
||||
```
|
||||
|
||||
The `matches()` method will return true IFF the passed in object has a
|
||||
key matching `attribute` and the "regex" matches the value
|
||||
|
||||
```js
|
||||
const f = new SubstringFilter({
|
||||
attribute: 'cn',
|
||||
initial: 'foo',
|
||||
any: ['bar'],
|
||||
final: 'baz'
|
||||
});
|
||||
|
||||
f.matches({cn: 'foobigbardogbaz'}); => true
|
||||
f.matches({sn: 'fobigbardogbaz'}); => false
|
||||
```
|
||||
|
||||
# GreaterThanEqualsFilter
|
||||
|
||||
The ge filter is used to do comparisons and ordering based on the value type. As
|
||||
mentioned elsewhere, by default everything in LDAP and ldapjs is a string, so
|
||||
this filter's `matches()` would be using lexicographical ordering of strings.
|
||||
If you wanted `>=` semantics over numeric values, you would need to add some
|
||||
middleware to convert values before comparison (and the value of the filter).
|
||||
Note that the ldapjs schema middleware will do this.
|
||||
|
||||
The GreaterThanEqualsFilter will have an `attribute` property, a `value`
|
||||
property and the `name` property will be `ge`.
|
||||
|
||||
The string syntax for a ge filter is:
|
||||
|
||||
```
|
||||
(cn>=foo)
|
||||
```
|
||||
|
||||
The `matches()` method will return true IFF the passed in object has a
|
||||
key matching `attribute` and the value is `>=` this filter's `value`.
|
||||
|
||||
```js
|
||||
const f = new GreaterThanEqualsFilter({
|
||||
attribute: 'cn',
|
||||
value: 'foo',
|
||||
});
|
||||
|
||||
f.matches({cn: 'foobar'}); => true
|
||||
f.matches({cn: 'abc'}); => false
|
||||
```
|
||||
|
||||
# LessThanEqualsFilter
|
||||
|
||||
The le filter is used to do comparisons and ordering based on the value type. As
|
||||
mentioned elsewhere, by default everything in LDAP and ldapjs is a string, so
|
||||
this filter's `matches()` would be using lexicographical ordering of strings.
|
||||
If you wanted `<=` semantics over numeric values, you would need to add some
|
||||
middleware to convert values before comparison (and the value of the filter).
|
||||
Note that the ldapjs schema middleware will do this.
|
||||
|
||||
The string syntax for a le filter is:
|
||||
|
||||
```
|
||||
(cn<=foo)
|
||||
```
|
||||
|
||||
The LessThanEqualsFilter will have an `attribute` property, a `value`
|
||||
property and the `name` property will be `le`.
|
||||
|
||||
The `matches()` method will return true IFF the passed in object has a
|
||||
key matching `attribute` and the value is `<=` this filter's `value`.
|
||||
|
||||
```js
|
||||
const f = new LessThanEqualsFilter({
|
||||
attribute: 'cn',
|
||||
value: 'foo',
|
||||
});
|
||||
|
||||
f.matches({cn: 'abc'}); => true
|
||||
f.matches({cn: 'foobar'}); => false
|
||||
```
|
||||
|
||||
# AndFilter
|
||||
|
||||
The and filter is a complex filter that simply contains "child" filters. The
|
||||
object will have a `filters` property which is an array of `Filter` objects. The
|
||||
`name` property will be `and`.
|
||||
|
||||
The string syntax for an and filter is (assuming below we're and'ing two
|
||||
equality filters):
|
||||
|
||||
```
|
||||
(&(cn=foo)(sn=bar))
|
||||
```
|
||||
|
||||
The `matches()` method will return true IFF the passed in object matches all
|
||||
the filters in the `filters` array.
|
||||
|
||||
```js
|
||||
const f = new AndFilter({
|
||||
filters: [
|
||||
new EqualityFilter({
|
||||
attribute: 'cn',
|
||||
value: 'foo'
|
||||
}),
|
||||
new EqualityFilter({
|
||||
attribute: 'sn',
|
||||
value: 'bar'
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
f.matches({cn: 'foo', sn: 'bar'}); => true
|
||||
f.matches({cn: 'foo', sn: 'baz'}); => false
|
||||
```
|
||||
|
||||
# OrFilter
|
||||
|
||||
The or filter is a complex filter that simply contains "child" filters. The
|
||||
object will have a `filters` property which is an array of `Filter` objects. The
|
||||
`name` property will be `or`.
|
||||
|
||||
The string syntax for an or filter is (assuming below we're or'ing two
|
||||
equality filters):
|
||||
|
||||
```
|
||||
(|(cn=foo)(sn=bar))
|
||||
```
|
||||
|
||||
The `matches()` method will return true IFF the passed in object matches *any*
|
||||
of the filters in the `filters` array.
|
||||
|
||||
```js
|
||||
const f = new OrFilter({
|
||||
filters: [
|
||||
new EqualityFilter({
|
||||
attribute: 'cn',
|
||||
value: 'foo'
|
||||
}),
|
||||
new EqualityFilter({
|
||||
attribute: 'sn',
|
||||
value: 'bar'
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
f.matches({cn: 'foo', sn: 'baz'}); => true
|
||||
f.matches({cn: 'bar', sn: 'baz'}); => false
|
||||
```
|
||||
|
||||
# NotFilter
|
||||
|
||||
The not filter is a complex filter that contains a single "child" filter. The
|
||||
object will have a `filter` property which is an instance of a `Filter` object.
|
||||
The `name` property will be `not`.
|
||||
|
||||
The string syntax for a not filter is (assuming below we're not'ing an
|
||||
equality filter):
|
||||
|
||||
```
|
||||
(!(cn=foo))
|
||||
```
|
||||
|
||||
The `matches()` method will return true IFF the passed in object does not match
|
||||
the filter in the `filter` property.
|
||||
|
||||
```js
|
||||
const f = new NotFilter({
|
||||
filter: new EqualityFilter({
|
||||
attribute: 'cn',
|
||||
value: 'foo'
|
||||
})
|
||||
});
|
||||
|
||||
f.matches({cn: 'bar'}); => true
|
||||
f.matches({cn: 'foo'}); => false
|
||||
```
|
||||
|
||||
# ApproximateFilter
|
||||
|
||||
The approximate filter is used to check "approximate" matching of
|
||||
attribute/value assertions. This object will have an `attribute` and
|
||||
`value` property, and the `name` property will be `approx`.
|
||||
|
||||
As a side point, this is a useless filter. It's really only here if you have
|
||||
some whacky client that's sending this. It just does an exact match (which
|
||||
is what ActiveDirectory does too).
|
||||
|
||||
The string syntax for an equality filter is `(attr~=value)`.
|
||||
|
||||
The `matches()` method will return true IFF the passed in object has a
|
||||
key matching `attribute` and a value exactly matching `value`.
|
||||
|
||||
```js
|
||||
const f = new ApproximateFilter({
|
||||
attribute: 'cn',
|
||||
value: 'foo'
|
||||
});
|
||||
|
||||
f.matches({cn: 'foo'}); => true
|
||||
f.matches({cn: 'bar'}); => false
|
||||
```
|
||||
697
node_modules/ldapjs/docs/guide.md
generated
vendored
Normal file
697
node_modules/ldapjs/docs/guide.md
generated
vendored
Normal file
@ -0,0 +1,697 @@
|
||||
---
|
||||
title: LDAP Guide | ldapjs
|
||||
---
|
||||
|
||||
# LDAP Guide
|
||||
|
||||
<div class="intro">
|
||||
|
||||
This guide was written assuming that you (1) don't know anything about ldapjs,
|
||||
and perhaps more importantly (2) know little, if anything about LDAP. If you're
|
||||
already an LDAP whiz, please don't read this and feel it's condescending. Most
|
||||
people don't know how LDAP works, other than that "it's that thing that has my
|
||||
password."
|
||||
|
||||
By the end of this guide, we'll have a simple LDAP server that accomplishes a
|
||||
"real" task.
|
||||
|
||||
</div>
|
||||
|
||||
# What exactly is LDAP?
|
||||
|
||||
If you haven't already read the
|
||||
[wikipedia](http://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol)
|
||||
entry (which you should go do right now), LDAP is the "Lightweight Directory
|
||||
Access Protocol". A directory service basically breaks down as follows:
|
||||
|
||||
* A directory is a tree of entries (similar to but different than an FS).
|
||||
* Every entry has a unique name in the tree.
|
||||
* An entry is a set of attributes.
|
||||
* An attribute is a key/value(s) pairing (multivalue is natural).
|
||||
|
||||
It might be helpful to visualize:
|
||||
|
||||
```
|
||||
o=example
|
||||
/ \
|
||||
ou=users ou=groups
|
||||
/ | | \
|
||||
cn=john cn=jane cn=dudes cn=dudettes
|
||||
/
|
||||
keyid=foo
|
||||
```
|
||||
|
||||
Let's say we wanted to look at the record cn=john:
|
||||
|
||||
```shell
|
||||
dn: cn=john, ou=users, o=example
|
||||
cn: john
|
||||
sn: smith
|
||||
email: john@example.com
|
||||
email: john.smith@example.com
|
||||
objectClass: person
|
||||
```
|
||||
|
||||
A few things to note:
|
||||
|
||||
* All names in a directory tree are actually referred to as a _distinguished
|
||||
name_, or _dn_ for short. A dn is comprised of attributes that lead to that
|
||||
node in the tree, as shown above (the syntax is foo=bar, ...).
|
||||
* The root of the tree is at the right of the _dn_, which is inverted from a
|
||||
filesystem hierarchy.
|
||||
* Every entry in the tree is an _instance of_ an _objectclass_.
|
||||
* An _objectclass_ is a schema concept; think of it like a table in a
|
||||
traditional ORM.
|
||||
* An _objectclass_ defines what _attributes_ an entry can have (on the ORM
|
||||
analogy, an _attribute_ would be like a column).
|
||||
|
||||
That's it. LDAP, then, is the protocol for interacting with the directory tree,
|
||||
and it's comprehensively specified for common operations, like
|
||||
add/update/delete and importantly, search. Really, the power of LDAP comes
|
||||
through the search operations defined in the protocol, which are richer
|
||||
than HTTP query string filtering, but less powerful than full SQL. You can
|
||||
think of LDAP as a NoSQL/document store with a well-defined query syntax.
|
||||
|
||||
So, why isn't LDAP more popular for a lot of applications? Like anything else
|
||||
that has "simple" or "lightweight" in the name, it's not really that
|
||||
lightweight. In particular, almost all of the implementations of LDAP stem
|
||||
from the original University of Michigan codebase written in 1996. At that
|
||||
time, the original intention of LDAP was to be an IP-accessible gateway to the
|
||||
much more complex X.500 directories, which means that a lot of that
|
||||
baggage has carried through to today. That makes for a high barrier to entry,
|
||||
when most applications just don't need most of those features.
|
||||
|
||||
## How is ldapjs any different?
|
||||
|
||||
Well, on the one hand, since ldapjs has to be 100% wire compatible with LDAP to
|
||||
be useful, it's not. On the other hand, there are no forced assumptions about
|
||||
what you need and don't need for your use of a directory system. For example,
|
||||
want to run with no-schema in OpenLDAP/389DS/et al? Good luck. Most of the
|
||||
server implementations support arbitrary "backends" for persistence, but really
|
||||
you'll be using [BDB](http://www.oracle.com/technetwork/database/berkeleydb/overview/index.html).
|
||||
|
||||
Want to run schema-less in ldapjs, or wire it up with some mongoose models? No
|
||||
problem. Want to back it to redis? Should be able to get some basics up in a
|
||||
day or two.
|
||||
|
||||
Basically, the ldapjs philosophy is to deal with the "muck" of LDAP, and then
|
||||
get out of the way so you can just use the "good parts."
|
||||
|
||||
# Ok, cool. Learn me some LDAP!
|
||||
|
||||
With the initial fluff out of the way, let's do something crazy to teach
|
||||
you some LDAP. Let's put an LDAP server up over the top of your (Linux) host's
|
||||
/etc/passwd and /etc/group files. Usually sysadmins "go the other way," and
|
||||
replace /etc/passwd with a
|
||||
[PAM](http://en.wikipedia.org/wiki/Pluggable_authentication_module "Pluggable
|
||||
authentication module") module to LDAP. While this is probably not a super
|
||||
useful real-world use case, it will teach you some of the basics. If it is
|
||||
useful to you, then that's gravy.
|
||||
|
||||
## Install
|
||||
|
||||
If you don't already have node.js and npm, clearly you need those, so follow
|
||||
the steps at [nodejs.org](http://nodejs.org) and [npmjs.org](http://npmjs.org),
|
||||
respectively. After that, run:
|
||||
|
||||
```shell
|
||||
$ npm install ldapjs
|
||||
```
|
||||
|
||||
Rather than overload you with client-side programming for now, we'll use
|
||||
the OpenLDAP CLI to interact with our server. It's almost certainly already
|
||||
installed on your system, but if not, you can get it from brew/apt/yum/your
|
||||
package manager here.
|
||||
|
||||
To get started, open some file, and let's get the library loaded and a server
|
||||
created:
|
||||
|
||||
```js
|
||||
const ldap = require('ldapjs');
|
||||
|
||||
const server = ldap.createServer();
|
||||
|
||||
server.listen(1389, () => {
|
||||
console.log('/etc/passwd LDAP server up at: %s', server.url);
|
||||
});
|
||||
```
|
||||
|
||||
And run that. Doing anything will give you errors (LDAP "No Such Object")
|
||||
since we haven't added any support in yet, but go ahead and try it anyway:
|
||||
|
||||
```shell
|
||||
$ ldapsearch -H ldap://localhost:1389 -x -b "o=myhost" objectclass=*
|
||||
```
|
||||
|
||||
Before we go any further, note that the complete code for the server we are
|
||||
about to build up is on the [examples](examples.html) page.
|
||||
|
||||
## Bind
|
||||
|
||||
So, lesson #1 about LDAP: unlike HTTP, it's connection-oriented; that means that
|
||||
you authenticate (in LDAP nomenclature this is called a _bind_), and all
|
||||
subsequent operations operate at the level of priviledge you established during
|
||||
a bind. You can bind any number of times on a single connection and change that
|
||||
identity. Technically, it's optional, and you can support _anonymous_
|
||||
operations from clients, but (1) you probably don't want that, and (2) most
|
||||
LDAP clients will initiate a bind anyway (OpenLDAP will), so let's add it in
|
||||
and get it out of our way.
|
||||
|
||||
What we're going to do is add a "root" user to our LDAP server. This root user
|
||||
has no correspondence to our Unix root user, it's just something we're making up
|
||||
and going to use for allowing an (LDAP) admin to do anything. To do so, add
|
||||
this code into your file:
|
||||
|
||||
```js
|
||||
server.bind('cn=root', (req, res, next) => {
|
||||
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
|
||||
return next(new ldap.InvalidCredentialsError());
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
```
|
||||
|
||||
Not very secure, but this is a demo. What we did there was "mount" a tree in
|
||||
the ldapjs server, and add a handler for the _bind_ method. If you've ever used
|
||||
express, this pattern should be really familiar; you can add any number of
|
||||
handlers in, as we'll see later.
|
||||
|
||||
On to the meat of the method. What's up with this?
|
||||
|
||||
```js
|
||||
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
|
||||
```
|
||||
|
||||
The first part `req.dn.toString() !== 'cn=root'`: you're probably thinking
|
||||
"WTF?!? Does ldapjs allow something other than cn=root into this handler?" Sort
|
||||
of. It allows cn=root *and any children* into that handler. So the entries
|
||||
`cn=root` and `cn=evil, cn=root` would both match and flow into this handler.
|
||||
Hence that check. The second check `req.credentials` is probably obvious, but
|
||||
it brings up an important point, and that is the `req`, `res` objects in ldapjs
|
||||
are not homogenous across server operation types. Unlike HTTP, there's not a
|
||||
single message format, so each of the operations has fields and functions
|
||||
appropriate to that type. The LDAP bind operation has `credentials`, which are
|
||||
a string representation of the client's password. This is logically the same as
|
||||
HTTP Basic Authentication (there are other mechanisms, but that's out of scope
|
||||
for a getting started guide). Ok, if either of those checks failed, we pass a
|
||||
new ldapjs `Error` back into the server, and it will (1) halt the chain, and (2)
|
||||
send the proper error code back to the client.
|
||||
|
||||
Lastly, assuming that this request was ok, we just end the operation with
|
||||
`res.end()`. The `return next()` isn't strictly necessary, since here we only
|
||||
have one handler in the chain, but it's good habit to always do that, so if you
|
||||
add another handler in later you won't get bit by it not being invoked.
|
||||
|
||||
Blah blah, let's try running the ldap client again, first with a bad password:
|
||||
|
||||
```shell
|
||||
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w foo -b "o=myhost" objectclass=*
|
||||
|
||||
ldap_bind: Invalid credentials (49)
|
||||
matched DN: cn=root
|
||||
additional info: Invalid Credentials
|
||||
```
|
||||
|
||||
And again with the correct one:
|
||||
|
||||
```shell
|
||||
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" objectclass=*
|
||||
|
||||
No such object (32)
|
||||
Additional information: No tree found for: o=myhost
|
||||
```
|
||||
|
||||
Don't worry about all the flags we're passing into OpenLDAP, that's just to make
|
||||
their CLI less annonyingly noisy. This time, we got another `No such object`
|
||||
error, but it's for the tree `o=myhost`. That means our bind went through, and
|
||||
our search failed, since we haven't yet added a search handler. Just one more
|
||||
small thing to do first.
|
||||
|
||||
Remember earlier I said there were no authorization rules baked into LDAP? Well,
|
||||
we added a bind route, so the only user that can authenticate is `cn=root`, but
|
||||
what if the remote end doesn't authenticate at all? Right, nothing says they
|
||||
*have to* bind, that's just what the common clients do. Let's add a quick
|
||||
authorization handler that we'll use in all our subsequent routes:
|
||||
|
||||
```js
|
||||
function authorize(req, res, next) {
|
||||
if (!req.connection.ldap.bindDN.equals('cn=root'))
|
||||
return next(new ldap.InsufficientAccessRightsError());
|
||||
|
||||
return next();
|
||||
}
|
||||
```
|
||||
|
||||
Should be pretty self-explanatory, but as a reminder, LDAP is connection
|
||||
oriented, so we check that the connection remote user was indeed our `cn=root`
|
||||
(by default ldapjs will have a DN of `cn=anonymous` if the client didn't bind).
|
||||
|
||||
## Search
|
||||
|
||||
We said we wanted to allow LDAP operations over /etc/passwd, so let's detour
|
||||
for a moment to explain an /etc/passwd record.
|
||||
|
||||
```shell
|
||||
jsmith:x:1001:1000:Joe Smith,Room 1007,(234)555-8910,(234)555-0044,email:/home/jsmith:/bin/sh
|
||||
```
|
||||
|
||||
The sample record above maps to:
|
||||
|
||||
|Field |Description |
|
||||
|-------------------|-----------------------------------|
|
||||
|jsmith |Username |
|
||||
|x |Placeholder for password hash |
|
||||
|1001 |Numeric UID |
|
||||
|1000 |Numeric Primary GID |
|
||||
|'Joe Smith,...' |DisplayName |
|
||||
|/home/jsmith |Home directory |
|
||||
|/bin/sh |Shell |
|
||||
|
||||
Let's write some handlers to parse that and transform it into an LDAP search
|
||||
record (note, you'll need to add `const fs = require('fs');` at the top of the
|
||||
source file).
|
||||
|
||||
First, make a handler that just loads the "user database" in a "pre" handler:
|
||||
|
||||
```js
|
||||
function loadPasswdFile(req, res, next) {
|
||||
fs.readFile('/etc/passwd', 'utf8', (err, data) => {
|
||||
if (err)
|
||||
return next(new ldap.OperationsError(err.message));
|
||||
|
||||
req.users = {};
|
||||
|
||||
const lines = data.split('\n');
|
||||
for (const line of lines) {
|
||||
if (!line || /^#/.test(line))
|
||||
continue;
|
||||
|
||||
const record = line.split(':');
|
||||
if (!record || !record.length)
|
||||
continue;
|
||||
|
||||
req.users[record[0]] = {
|
||||
dn: 'cn=' + record[0] + ', ou=users, o=myhost',
|
||||
attributes: {
|
||||
cn: record[0],
|
||||
uid: record[2],
|
||||
gid: record[3],
|
||||
description: record[4],
|
||||
homedirectory: record[5],
|
||||
shell: record[6] || '',
|
||||
objectclass: 'unixUser'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Ok, all that did is tack the /etc/passwd records onto req.users so that any
|
||||
subsequent handler doesn't have to reload the file. Next, let's write a search
|
||||
handler to process that:
|
||||
|
||||
```js
|
||||
const pre = [authorize, loadPasswdFile];
|
||||
|
||||
server.search('o=myhost', pre, (req, res, next) => {
|
||||
const keys = Object.keys(req.users);
|
||||
for (const k of keys) {
|
||||
if (req.filter.matches(req.users[k].attributes))
|
||||
res.send(req.users[k]);
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
```
|
||||
|
||||
And try running:
|
||||
|
||||
```shell
|
||||
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" cn=root
|
||||
dn: cn=root, ou=users, o=myhost
|
||||
cn: root
|
||||
uid: 0
|
||||
gid: 0
|
||||
description: System Administrator
|
||||
homedirectory: /var/root
|
||||
shell: /bin/sh
|
||||
objectclass: unixUser
|
||||
```
|
||||
|
||||
Sweet! Try this out too:
|
||||
|
||||
```shell
|
||||
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" objectclass=*
|
||||
...
|
||||
```
|
||||
|
||||
You should have seen an entry for every record in /etc/passwd with the second.
|
||||
What all did we do here? A lot. Let's break this down...
|
||||
|
||||
### What did I just do on the command line?
|
||||
|
||||
Let's start with looking at what you even asked for:
|
||||
|
||||
```shell
|
||||
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" cn=root
|
||||
```
|
||||
|
||||
We can throw away `ldapsearch -H -x -D -w -LLL`, as those just specify the URL
|
||||
to connect to, the bind credentials and the `-LLL` just quiets down OpenLDAP.
|
||||
That leaves us with: `-b "o=myhost" cn=root`.
|
||||
|
||||
The `-b o=myhost` tells our LDAP server where to _start_ looking in
|
||||
the tree for entries that might match the search filter, which above is
|
||||
`cn=root`.
|
||||
|
||||
In this little LDAP example, we're mostly throwing out any qualification of the
|
||||
"tree," since there's not actually a tree in /etc/passwd (we will extend later
|
||||
with /etc/group). Remember how I said ldapjs gets out of the way and doesn't
|
||||
force anything on you? Here's an example. If we wanted an LDAP server to run
|
||||
over the filesystem, we actually would use this, but here, meh.
|
||||
|
||||
Next, `cn=root` is the search "filter". LDAP has a rich specification of
|
||||
filters, where you can specify `and`, `or`, `not`, `>=`, `<=`, `equal`,
|
||||
`wildcard`, `present` and a few other esoteric things. Really, `equal`,
|
||||
`wildcard`, `present` and the boolean operators are all you'll likely ever need.
|
||||
So, the filter `cn=root` is an "equality" filter, and says to only return
|
||||
entries that have attributes that match that. In the second invocation, we used
|
||||
a 'presence' filter, to say 'return any entries that have an objectclass'
|
||||
attribute, which in LDAP parlance is saying "give me everything."
|
||||
|
||||
### The code
|
||||
|
||||
In the code above, let's ignore the fs and split stuff, since really all we
|
||||
did was read in /etc/passwd line by line. After that, we looked at each record
|
||||
and made the cheesiest transform ever, which is making up a "search entry." A
|
||||
search entry _must_ have a DN so the client knows what record it is, and a set
|
||||
of attributes. So that's why we did this:
|
||||
|
||||
```js
|
||||
const entry = {
|
||||
dn: 'cn=' + record[0] + ', ou=users, o=myhost',
|
||||
attributes: {
|
||||
cn: record[0],
|
||||
uid: record[2],
|
||||
gid: record[3],
|
||||
description: record[4],
|
||||
homedirectory: record[5],
|
||||
shell: record[6] || '',
|
||||
objectclass: 'unixUser'
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Next, we let ldapjs do all the hard work of figuring out LDAP search filters
|
||||
for us by calling `req.filter.matches`. If it matched, we return the whole
|
||||
record with `res.send`. In this little example we're running O(n), so for
|
||||
something big and/or slow, you'd have to do some work to effectively write a
|
||||
query planner (or just not support it...). For some reference code, check out
|
||||
`node-ldapjs-riak`, which takes on the fairly difficult task of writing a 'full'
|
||||
LDAP server over riak.
|
||||
|
||||
To demonstrate what ldapjs is doing for you, let's find all users who have a
|
||||
shell set to `/bin/false` and whose name starts with `p` (I'm doing this
|
||||
on Ubuntu). Then, let's say we only care about their login name and primary
|
||||
group id. We'd do this:
|
||||
|
||||
```shell
|
||||
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" "(&(shell=/bin/false)(cn=p*))" cn gid
|
||||
dn: cn=proxy, ou=users, o=myhost
|
||||
cn: proxy
|
||||
gid: 13
|
||||
|
||||
dn: cn=pulse, ou=users, o=myhost
|
||||
cn: pulse
|
||||
gid: 114
|
||||
```
|
||||
|
||||
## Add
|
||||
|
||||
This is going to be a little bit ghetto, since what we're going to do is just
|
||||
use node's child process module to spawn calls to `adduser`. Go ahead and add
|
||||
the following code in as another handler (you'll need a
|
||||
`const { spawn } = require('child_process');` at the top of your file):
|
||||
|
||||
```js
|
||||
server.add('ou=users, o=myhost', pre, (req, res, next) => {
|
||||
if (!req.dn.rdns[0].attrs.cn)
|
||||
return next(new ldap.ConstraintViolationError('cn required'));
|
||||
|
||||
if (req.users[req.dn.rdns[0].attrs.cn.value])
|
||||
return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
|
||||
|
||||
const entry = req.toObject().attributes;
|
||||
|
||||
if (entry.objectclass.indexOf('unixUser') === -1)
|
||||
return next(new ldap.ConstraintViolationError('entry must be a unixUser'));
|
||||
|
||||
const opts = ['-m'];
|
||||
if (entry.description) {
|
||||
opts.push('-c');
|
||||
opts.push(entry.description[0]);
|
||||
}
|
||||
if (entry.homedirectory) {
|
||||
opts.push('-d');
|
||||
opts.push(entry.homedirectory[0]);
|
||||
}
|
||||
if (entry.gid) {
|
||||
opts.push('-g');
|
||||
opts.push(entry.gid[0]);
|
||||
}
|
||||
if (entry.shell) {
|
||||
opts.push('-s');
|
||||
opts.push(entry.shell[0]);
|
||||
}
|
||||
if (entry.uid) {
|
||||
opts.push('-u');
|
||||
opts.push(entry.uid[0]);
|
||||
}
|
||||
opts.push(entry.cn[0]);
|
||||
const useradd = spawn('useradd', opts);
|
||||
|
||||
const messages = [];
|
||||
|
||||
useradd.stdout.on('data', (data) => {
|
||||
messages.push(data.toString());
|
||||
});
|
||||
useradd.stderr.on('data', (data) => {
|
||||
messages.push(data.toString());
|
||||
});
|
||||
|
||||
useradd.on('exit', (code) => {
|
||||
if (code !== 0) {
|
||||
let msg = '' + code;
|
||||
if (messages.length)
|
||||
msg += ': ' + messages.join();
|
||||
return next(new ldap.OperationsError(msg));
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Then, you'll need to be root to have this running, so start your server with
|
||||
`sudo` (or be root, whatever). Now, go ahead and create a file called
|
||||
`user.ldif` with the following contents:
|
||||
|
||||
```shell
|
||||
dn: cn=ldapjs, ou=users, o=myhost
|
||||
objectClass: unixUser
|
||||
cn: ldapjs
|
||||
shell: /bin/bash
|
||||
description: Created via ldapadd
|
||||
```
|
||||
|
||||
Now go ahead and invoke with:
|
||||
|
||||
```shell
|
||||
$ ldapadd -H ldap://localhost:1389 -x -D cn=root -w secret -f ./user.ldif
|
||||
adding new entry "cn=ldapjs, ou=users, o=myhost"
|
||||
```
|
||||
|
||||
Let's confirm he got added with an ldapsearch:
|
||||
|
||||
```shell
|
||||
$ ldapsearch -H ldap://localhost:1389 -LLL -x -D cn=root -w secret -b "ou=users, o=myhost" cn=ldapjs
|
||||
dn: cn=ldapjs, ou=users, o=myhost
|
||||
cn: ldapjs
|
||||
uid: 1001
|
||||
gid: 1001
|
||||
description: Created via ldapadd
|
||||
homedirectory: /home/ldapjs
|
||||
shell: /bin/bash
|
||||
objectclass: unixUser
|
||||
```
|
||||
|
||||
As before, here's a breakdown of the code:
|
||||
|
||||
```js
|
||||
server.add('ou=users, o=myhost', pre, (req, res, next) => {
|
||||
if (!req.dn.rdns[0].attrs.cn)
|
||||
return next(new ldap.ConstraintViolationError('cn required'));
|
||||
|
||||
if (req.users[req.dn.rdns[0].attrs.cn.value])
|
||||
return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
|
||||
|
||||
const entry = req.toObject().attributes;
|
||||
|
||||
if (entry.objectclass.indexOf('unixUser') === -1)
|
||||
return next(new ldap.ConstraintViolationError('entry must be a unixUser'));
|
||||
});
|
||||
```
|
||||
|
||||
A few new things:
|
||||
|
||||
* We mounted this handler at `ou=users, o=myhost`. Why? What if we want to
|
||||
extend this little project with groups? We probably want those under a
|
||||
different part of the tree.
|
||||
* We did some really minimal schema enforcement by:
|
||||
+ Checking that the leaf RDN (relative distinguished name) was a _cn_
|
||||
attribute.
|
||||
+ We then did `req.toObject()`. As mentioned before, each of the req/res
|
||||
objects have special APIs that make sense for that operation. Without getting
|
||||
into the details, the LDAP add operation on the wire doesn't look like a JS
|
||||
object, and we want to support both the LDAP nerd that wants to see what
|
||||
got sent, and the "easy" case. So use `.toObject()`. Note we also filtered
|
||||
out to the `attributes` portion of the object since that's all we're really
|
||||
looking at.
|
||||
+ Lastly, we did a super minimal check to see if the entry was of type
|
||||
`unixUser`. Frankly for this case, it's kind of useless, but it does illustrate
|
||||
one point: attribute names are case-insensitive, so ldapjs converts them all to
|
||||
lower case (note the client sent _objectClass_ over the wire).
|
||||
|
||||
After that, we really just delegated off to the _useradd_ command. As far as I
|
||||
know, there is not a node.js module that wraps up `getpwent` and friends,
|
||||
otherwise we'd use that.
|
||||
|
||||
Now, what's missing? Oh, right, we need to let you set a password. Well, let's
|
||||
support that via the _modify_ command.
|
||||
|
||||
## Modify
|
||||
|
||||
Unlike HTTP, "partial" document updates are fully specified as part of the
|
||||
RFC, so appending, removing, or replacing a single attribute is pretty natural.
|
||||
Go ahead and add the following code into your source file:
|
||||
|
||||
```js
|
||||
server.modify('ou=users, o=myhost', pre, (req, res, next) => {
|
||||
if (!req.dn.rdns[0].attrs.cn || !req.users[req.dn.rdns[0].attrs.cn.value])
|
||||
return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
if (!req.changes.length)
|
||||
return next(new ldap.ProtocolError('changes required'));
|
||||
|
||||
const user = req.users[req.dn.rdns[0].attrs.cn.value].attributes;
|
||||
let mod;
|
||||
|
||||
for (const i = 0; i < req.changes.length; i++) {
|
||||
mod = req.changes[i].modification;
|
||||
switch (req.changes[i].operation) {
|
||||
case 'replace':
|
||||
if (mod.type !== 'userpassword' || !mod.vals || !mod.vals.length)
|
||||
return next(new ldap.UnwillingToPerformError('only password updates ' +
|
||||
'allowed'));
|
||||
break;
|
||||
case 'add':
|
||||
case 'delete':
|
||||
return next(new ldap.UnwillingToPerformError('only replace allowed'));
|
||||
}
|
||||
}
|
||||
|
||||
const passwd = spawn('chpasswd', ['-c', 'MD5']);
|
||||
passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8');
|
||||
|
||||
passwd.on('exit', (code) => {
|
||||
if (code !== 0)
|
||||
return next(new ldap.OperationsError(code));
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Basically, we made sure the remote client was targeting an entry that exists,
|
||||
ensuring that they were asking to "replace" the `userPassword` attribute (which
|
||||
is the 'standard' LDAP attribute for passwords; if you think it's easier to use
|
||||
'password', knock yourself out), and then just delegating to the `chpasswd`
|
||||
command (which lets you change a user's password over stdin). Next, go ahead
|
||||
and create a `passwd.ldif` file:
|
||||
|
||||
```shell
|
||||
dn: cn=ldapjs, ou=users, o=myhost
|
||||
changetype: modify
|
||||
replace: userPassword
|
||||
userPassword: secret
|
||||
-
|
||||
```
|
||||
|
||||
And then run the OpenLDAP CLI:
|
||||
|
||||
```shell
|
||||
$ ldapmodify -H ldap://localhost:1389 -x -D cn=root -w secret -f ./passwd.ldif
|
||||
```
|
||||
|
||||
You should now be able to login to your box as the ldapjs user. Let's get
|
||||
the last "mainline" piece of work out of the way, and delete the user.
|
||||
|
||||
## Delete
|
||||
|
||||
Delete is pretty straightforward. The client gives you a dn to delete, and you
|
||||
delete it :). Add the following code into your server:
|
||||
|
||||
```js
|
||||
server.del('ou=users, o=myhost', pre, (req, res, next) => {
|
||||
if (!req.dn.rdns[0].attrs.cn || !req.users[req.dn.rdns[0].attrs.cn.value])
|
||||
return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
const userdel = spawn('userdel', ['-f', req.dn.rdns[0].attrs.cn.value]);
|
||||
|
||||
const messages = [];
|
||||
userdel.stdout.on('data', (data) => {
|
||||
messages.push(data.toString());
|
||||
});
|
||||
userdel.stderr.on('data', (data) => {
|
||||
messages.push(data.toString());
|
||||
});
|
||||
|
||||
userdel.on('exit', (code) => {
|
||||
if (code !== 0) {
|
||||
let msg = '' + code;
|
||||
if (messages.length)
|
||||
msg += ': ' + messages.join();
|
||||
return next(new ldap.OperationsError(msg));
|
||||
}
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
And then run the following command:
|
||||
|
||||
```shell
|
||||
$ ldapdelete -H ldap://localhost:1389 -x -D cn=root -w secret "cn=ldapjs, ou=users, o=myhost"
|
||||
```
|
||||
|
||||
# Where to go from here
|
||||
|
||||
The complete source code for this example server is available in
|
||||
[examples](examples.html). Make sure to read up on the [server](server.html)
|
||||
and [client](client.html) APIs. If you're looking for a "drop in" solution,
|
||||
take a look at [ldapjs-riak](https://github.com/mcavage/node-ldapjs-riak).
|
||||
|
||||
[Mozilla](https://wiki.mozilla.org/Mozilla_LDAP_SDK_Programmer%27s_Guide/Understanding_LDAP)
|
||||
still maintains some web pages with LDAP overviews if you look around, if you're
|
||||
looking for more tutorials. After that, you'll need to work your way through
|
||||
the [RFCs](http://tools.ietf.org/html/rfc4510) as you work through the APIs in
|
||||
ldapjs.
|
||||
95
node_modules/ldapjs/docs/index.md
generated
vendored
Normal file
95
node_modules/ldapjs/docs/index.md
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
---
|
||||
title: ldapjs
|
||||
---
|
||||
|
||||
<div id="indextagline">
|
||||
Reimagining <a href="http://tools.ietf.org/html/rfc4510" id="indextaglink">LDAP</a> for <a id="indextaglink" href="http://nodejs.org">Node.js</a>
|
||||
</div>
|
||||
|
||||
# Overview
|
||||
|
||||
<div class="intro">
|
||||
|
||||
ldapjs is a pure JavaScript, from-scratch framework for implementing
|
||||
[LDAP](http://tools.ietf.org/html/rfc4510) clients and servers in
|
||||
[Node.js](http://nodejs.org). It is intended for developers used to interacting
|
||||
with HTTP services in node and [restify](http://restify.com).
|
||||
|
||||
</div>
|
||||
|
||||
```js
|
||||
const ldap = require('ldapjs');
|
||||
|
||||
const server = ldap.createServer();
|
||||
|
||||
server.search('o=example', (req, res, next) => {
|
||||
const obj = {
|
||||
dn: req.dn.toString(),
|
||||
attributes: {
|
||||
objectclass: ['organization', 'top'],
|
||||
o: 'example'
|
||||
}
|
||||
};
|
||||
|
||||
if (req.filter.matches(obj.attributes))
|
||||
res.send(obj);
|
||||
|
||||
res.end();
|
||||
});
|
||||
|
||||
server.listen(1389, () => {
|
||||
console.log('LDAP server listening at %s', server.url);
|
||||
});
|
||||
```
|
||||
|
||||
Try hitting that with:
|
||||
|
||||
```shell
|
||||
$ ldapsearch -H ldap://localhost:1389 -x -b o=example objectclass=*
|
||||
```
|
||||
|
||||
# Features
|
||||
|
||||
ldapjs implements most of the common operations in the LDAP v3 RFC(s), for
|
||||
both client and server. It is 100% wire-compatible with the LDAP protocol
|
||||
itself, and is interoperable with [OpenLDAP](http://openldap.org) and any other
|
||||
LDAPv3-compliant implementation. ldapjs gives you a powerful routing and
|
||||
"intercepting filter" pattern for implementing server(s). It is intended
|
||||
that you can build LDAP over anything you want, not just traditional databases.
|
||||
|
||||
# Getting started
|
||||
|
||||
```shell
|
||||
$ npm install ldapjs
|
||||
```
|
||||
|
||||
If you're new to LDAP, check out the [guide](guide.html). Otherwise, the
|
||||
API documentation is:
|
||||
|
||||
|
||||
|Section |Content |
|
||||
|---------------------------|-------------------------------------------|
|
||||
|[Server API](server.html) |Reference for implementing LDAP servers. |
|
||||
|[Client API](client.html) |Reference for implementing LDAP clients. |
|
||||
|[DN API](dn.html) |API reference for the DN class. |
|
||||
|[Filter API](filters.html) |API reference for LDAP search filters. |
|
||||
|[Error API](errors.html) |Listing of all ldapjs Error objects. |
|
||||
|[Examples](examples.html) |Collection of sample/getting started code. |
|
||||
|
||||
# More information
|
||||
|
||||
- License:[MIT](http://opensource.org/licenses/mit-license.php)
|
||||
- Code: [ldapjs/node-ldapjs](https://github.com/ldapjs/node-ldapjs)
|
||||
|
||||
# What's not in the box?
|
||||
|
||||
Since most developers and system(s) adminstrators struggle with some of the
|
||||
esoteric features of LDAP, not all features in LDAP are implemented here.
|
||||
Specifically:
|
||||
|
||||
* LDIF
|
||||
* Aliases
|
||||
* Attributes by OID
|
||||
* Extensible matching
|
||||
|
||||
There are a few others, but those are the "big" ones.
|
||||
614
node_modules/ldapjs/docs/server.md
generated
vendored
Normal file
614
node_modules/ldapjs/docs/server.md
generated
vendored
Normal file
@ -0,0 +1,614 @@
|
||||
---
|
||||
title: Server API | ldapjs
|
||||
---
|
||||
|
||||
# ldapjs Server API
|
||||
|
||||
<div class="intro">
|
||||
|
||||
This document covers the ldapjs server API and assumes that you are familiar
|
||||
with LDAP. If you're not, read the [guide](guide.html) first.
|
||||
|
||||
</div>
|
||||
|
||||
# Create a server
|
||||
|
||||
The code to create a new server looks like:
|
||||
|
||||
```js
|
||||
const server = ldap.createServer();
|
||||
```
|
||||
|
||||
The full list of options is:
|
||||
|
||||
||log||You can optionally pass in a Bunyan compatible logger instance the client will use to acquire a child logger.||
|
||||
||certificate||A PEM-encoded X.509 certificate; will cause this server to run in TLS mode.||
|
||||
||key||A PEM-encoded private key that corresponds to _certificate_ for SSL.||
|
||||
|
||||
### Note On Logger
|
||||
|
||||
The passed in logger is expected to conform to the Log4j standard API.
|
||||
Internally, [abstract-logging](https://www.npmjs.com/packages/abstract-logging) is
|
||||
used to implement the interface. As a result, no log messages will be generated
|
||||
unless an external logger is supplied.
|
||||
|
||||
Known compatible loggers are:
|
||||
|
||||
+ [Bunyan](https://www.npmjs.com/package/bunyan)
|
||||
+ [Pino](https://www.npmjs.com/package/pino)
|
||||
|
||||
## Properties on the server object
|
||||
|
||||
### maxConnections
|
||||
|
||||
Set this property to reject connections when the server's connection count gets
|
||||
high.
|
||||
|
||||
### connections (getter only) - DEPRECATED
|
||||
|
||||
The number of concurrent connections on the server. This property is deprecated,
|
||||
please use server.getConnections() instead.
|
||||
|
||||
### url
|
||||
|
||||
Returns the fully qualified URL this server is listening on. For example:
|
||||
`ldaps://10.1.2.3:1636`. If you haven't yet called `listen`, it will always
|
||||
return `ldap://localhost:389`.
|
||||
|
||||
### Event: 'close'
|
||||
`function() {}`
|
||||
|
||||
Emitted when the server closes.
|
||||
|
||||
## Listening for requests
|
||||
|
||||
The LDAP server API wraps up and mirrors the node.js `server.listen` family of
|
||||
APIs.
|
||||
|
||||
After calling `listen`, the property `url` on the server object itself will be
|
||||
available.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
server.listen(389, '127.0.0.1', function() {
|
||||
console.log('LDAP server listening at: ' + server.url);
|
||||
});
|
||||
```
|
||||
|
||||
### Port and Host
|
||||
`listen(port, [host], [callback])`
|
||||
|
||||
Begin accepting connections on the specified port and host. If the host is
|
||||
omitted, the server will accept connections directed to the IPv4 address
|
||||
`127.0.0.1`. To listen on any other address, supply said address as the `host`
|
||||
parameter. For example, to listen on all available IPv6 addresses supply
|
||||
`::` as the `host` (note, this _may_ also result in listening on all
|
||||
available IPv4 addresses, depending on operating system behavior).
|
||||
|
||||
We highly recommend being as explicit as possible with the `host` parameter.
|
||||
Listening on all available addresses (through `::` or `0.0.0.0`) can lead
|
||||
to potential security issues.
|
||||
|
||||
This function is asynchronous. The last parameter callback will be called when
|
||||
the server has been bound.
|
||||
|
||||
### Unix Domain Socket
|
||||
`listen(path, [callback])`
|
||||
|
||||
Start a UNIX socket server listening for connections on the given path.
|
||||
|
||||
This function is asynchronous. The last parameter callback will be called when
|
||||
the server has been bound.
|
||||
|
||||
### File descriptor
|
||||
`listenFD(fd)`
|
||||
|
||||
Start a server listening for connections on the given file descriptor.
|
||||
|
||||
This file descriptor must have already had the `bind(2)` and `listen(2)` system
|
||||
calls invoked on it. Additionally, it must be set non-blocking; try
|
||||
`fcntl(fd, F_SETFL, O_NONBLOCK)`.
|
||||
|
||||
## Inspecting server state
|
||||
|
||||
### server.getConnections(callback)
|
||||
|
||||
The LDAP server API mirrors the [Node.js `server.getConnections` API](https://nodejs.org/dist/latest-v12.x/docs/api/net.html#net_server_getconnections_callback). Callback
|
||||
should take two arguments err and count.
|
||||
|
||||
# Routes
|
||||
|
||||
The LDAP server API is meant to be the LDAP-equivalent of the express/restify
|
||||
paradigm of programming. Essentially every method is of the form
|
||||
`OP(req, res, next)` where OP is one of bind, add, del, etc. You can chain
|
||||
handlers together by calling `next()` and ordering your functions in the
|
||||
definition of the route. For example:
|
||||
|
||||
```js
|
||||
function authorize(req, res, next) {
|
||||
if (!req.connection.ldap.bindDN.equals('cn=root'))
|
||||
return next(new ldap.InsufficientAccessRightsError());
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
server.search('o=example', authorize, function(req, res, next) { ... });
|
||||
```
|
||||
|
||||
Note that ldapjs is also slightly different, since it's often going to be backed
|
||||
to a DB-like entity, in that it also has an API where you can pass in a
|
||||
'backend' object. This is necessary if there are persistent connection pools,
|
||||
caching, etc. that need to be placed in an object.
|
||||
|
||||
For example [ldapjs-riak](https://github.com/mcavage/node-ldapjs-riak) is a
|
||||
complete implementation of the LDAP protocol over
|
||||
[Riak](https://github.com/basho/riak). Getting an LDAP server up with riak
|
||||
looks like:
|
||||
|
||||
```js
|
||||
const ldap = require('ldapjs');
|
||||
const ldapRiak = require('ldapjs-riak');
|
||||
|
||||
const server = ldap.createServer();
|
||||
const backend = ldapRiak.createBackend({
|
||||
"host": "localhost",
|
||||
"port": 8098,
|
||||
"bucket": "example",
|
||||
"indexes": ["l", "cn"],
|
||||
"uniqueIndexes": ["uid"],
|
||||
"numConnections": 5
|
||||
});
|
||||
|
||||
server.add("o=example",
|
||||
backend,
|
||||
backend.add());
|
||||
...
|
||||
```
|
||||
|
||||
The first parameter to an ldapjs route is always the point in the
|
||||
tree to mount the handler chain at. The second argument is _optionally_ a
|
||||
backend object. After that you can pass in an arbitrary combination of
|
||||
functions in the form `f(req, res, next)` or arrays of functions of the same
|
||||
signature (ldapjs will unroll them).
|
||||
|
||||
Unlike HTTP, LDAP operations do not have a heterogeneous wire format, so each
|
||||
operation requires specific methods/fields on the request/response
|
||||
objects. However, there is a `.use()` method availabe, similar to
|
||||
that on express/connect, allowing you to chain up "middleware":
|
||||
|
||||
```js
|
||||
server.use(function(req, res, next) {
|
||||
console.log('hello world');
|
||||
return next();
|
||||
});
|
||||
```
|
||||
|
||||
## Common Request Elements
|
||||
|
||||
All request objects have the `dn` getter on it, which is "context-sensitive"
|
||||
and returns the point in the tree that the operation wants to operate on. The
|
||||
LDAP protocol itself sadly doesn't define operations this way, and has a unique
|
||||
name for just about every op. So, ldapjs calls it `dn`. The DN object itself
|
||||
is documented at [DN](dn.html).
|
||||
|
||||
All requests have an optional array of `Control` objects. `Control` will have
|
||||
the properties `type` (string), `criticality` (boolean), and optionally, a
|
||||
string `value`.
|
||||
|
||||
All request objects will have a `connection` object, which is the `net.Socket`
|
||||
associated to this request. Off the `connection` object is an `ldap` object.
|
||||
The most important property to pay attention to is the `bindDN` property
|
||||
which will be an instance of an `ldap.DN` object. This is what the client
|
||||
authenticated as on this connection. If the client didn't bind, then a DN object
|
||||
will be there defaulted to `cn=anonymous`.
|
||||
|
||||
Additionally, request will have a `logId` parameter you can use to uniquely
|
||||
identify the request/connection pair in logs (includes the LDAP messageId).
|
||||
|
||||
## Common Response Elements
|
||||
|
||||
All response objects will have an `end` method on them. By default, calling
|
||||
`res.end()` with no arguments will return SUCCESS (0x00) to the client
|
||||
(with the exception of `compare` which will return COMPARE\_TRUE (0x06)). You
|
||||
can pass in a status code to the `end()` method to return an alternate status
|
||||
code.
|
||||
|
||||
However, it's more common/easier to use the `return next(new LDAPError())`
|
||||
pattern, since ldapjs will fill in the extra LDAPResult fields like matchedDN
|
||||
and error message for you.
|
||||
|
||||
## Errors
|
||||
|
||||
ldapjs includes an exception hierarchy that directly corresponds to the RFC list
|
||||
of error codes. The complete list is documented in [errors](errors.html). But
|
||||
the paradigm is something defined like CONSTRAINT\_VIOLATION in the RFC would be
|
||||
`ConstraintViolationError` in ldapjs. Upon calling `next(new LDAPError())`,
|
||||
ldapjs will _stop_ calling your handler chain. For example:
|
||||
|
||||
```js
|
||||
server.search('o=example',
|
||||
(req, res, next) => { return next(); },
|
||||
(req, res, next) => { return next(new ldap.OperationsError()); },
|
||||
(req, res, next) => { res.end(); }
|
||||
);
|
||||
```
|
||||
|
||||
In the code snipped above, the third handler would never get invoked.
|
||||
|
||||
# Bind
|
||||
|
||||
Adds a mount in the tree to perform LDAP binds with. Example:
|
||||
|
||||
```js
|
||||
server.bind('ou=people, o=example', (req, res, next) => {
|
||||
console.log('bind DN: ' + req.dn.toString());
|
||||
console.log('bind PW: ' + req.credentials);
|
||||
res.end();
|
||||
});
|
||||
```
|
||||
|
||||
## BindRequest
|
||||
|
||||
BindRequest objects have the following properties:
|
||||
|
||||
### version
|
||||
|
||||
The LDAP protocol version the client is requesting to run this connection on.
|
||||
Note that ldapjs only supports LDAP version 3.
|
||||
|
||||
### name
|
||||
|
||||
The DN the client is attempting to bind as (note this is the same as the `dn`
|
||||
property).
|
||||
|
||||
### authentication
|
||||
|
||||
The method of authentication. Right now only `simple` is supported.
|
||||
|
||||
### credentials
|
||||
|
||||
The credentials to go with the `name/authentication` pair. For `simple`, this
|
||||
will be the plain-text password.
|
||||
|
||||
## BindResponse
|
||||
|
||||
No extra methods above an `LDAPResult` API call.
|
||||
|
||||
# Add
|
||||
|
||||
Adds a mount in the tree to perform LDAP adds with.
|
||||
|
||||
```js
|
||||
server.add('ou=people, o=example', (req, res, next) => {
|
||||
console.log('DN: ' + req.dn.toString());
|
||||
console.log('Entry attributes: ' + req.toObject().attributes);
|
||||
res.end();
|
||||
});
|
||||
```
|
||||
|
||||
## AddRequest
|
||||
|
||||
AddRequest objects have the following properties:
|
||||
|
||||
### entry
|
||||
|
||||
The DN the client is attempting to add (this is the same as the `dn`
|
||||
property).
|
||||
|
||||
### attributes
|
||||
|
||||
The set of attributes in this entry. This will be an array of
|
||||
`Attribute` objects (which have a type and an array of values). This directly
|
||||
maps to how the request came in off the wire. It's likely you'll want to use
|
||||
`toObject()` and iterate that way, since that will transform an AddRequest into
|
||||
a standard JavaScript object.
|
||||
|
||||
### toObject()
|
||||
|
||||
This operation will return a plain JavaScript object from the request that looks
|
||||
like:
|
||||
|
||||
```js
|
||||
{
|
||||
dn: 'cn=foo, o=example', // string, not DN object
|
||||
attributes: {
|
||||
cn: ['foo'],
|
||||
sn: ['bar'],
|
||||
objectclass: ['person', 'top']
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## AddResponse
|
||||
|
||||
No extra methods above an `LDAPResult` API call.
|
||||
|
||||
# Search
|
||||
|
||||
Adds a handler for the LDAP search operation.
|
||||
|
||||
```js
|
||||
server.search('o=example', (req, res, next) => {
|
||||
console.log('base object: ' + req.dn.toString());
|
||||
console.log('scope: ' + req.scope);
|
||||
console.log('filter: ' + req.filter.toString());
|
||||
res.end();
|
||||
});
|
||||
```
|
||||
|
||||
## SearchRequest
|
||||
|
||||
SearchRequest objects have the following properties:
|
||||
|
||||
### baseObject
|
||||
|
||||
The DN the client is attempting to start the search at (equivalent to `dn`).
|
||||
|
||||
### scope
|
||||
|
||||
(string) one of:
|
||||
|
||||
* base
|
||||
* one
|
||||
* sub
|
||||
|
||||
### derefAliases
|
||||
|
||||
An integer (defined in the LDAP protocol). Defaults to '0' (meaning
|
||||
never deref).
|
||||
|
||||
### sizeLimit
|
||||
|
||||
The number of entries to return. Defaults to '0' (unlimited). ldapjs doesn't
|
||||
currently automatically enforce this, but probably will at some point.
|
||||
|
||||
### timeLimit
|
||||
|
||||
Maximum amount of time the server should take in sending search entries.
|
||||
Defaults to '0' (unlimited).
|
||||
|
||||
### typesOnly
|
||||
|
||||
Whether to return only the names of attributes, and not the values. Defaults to
|
||||
'false'. ldapjs will take care of this for you.
|
||||
|
||||
### filter
|
||||
|
||||
The [filter](filters.html) object that the client requested. Notably this has
|
||||
a `matches()` method on it that you can leverage. For an example of
|
||||
introspecting a filter, take a look at the ldapjs-riak source.
|
||||
|
||||
### attributes
|
||||
|
||||
An optional list of attributes to restrict the returned result sets to. ldapjs
|
||||
will automatically handle this for you.
|
||||
|
||||
## SearchResponse
|
||||
|
||||
### send(entry)
|
||||
|
||||
Allows you to send a `SearchEntry` object. You do not need to
|
||||
explicitly pass in a `SearchEntry` object, and can instead just send a plain
|
||||
JavaScript object that matches the format used from `AddRequest.toObject()`.
|
||||
|
||||
|
||||
```js
|
||||
server.search('o=example', (req, res, next) => {
|
||||
const obj = {
|
||||
dn: 'o=example',
|
||||
attributes: {
|
||||
objectclass: ['top', 'organization'],
|
||||
o: ['example']
|
||||
}
|
||||
};
|
||||
|
||||
if (req.filter.matches(obj))
|
||||
res.send(obj)
|
||||
|
||||
res.end();
|
||||
});
|
||||
```
|
||||
|
||||
# modify
|
||||
|
||||
Allows you to handle an LDAP modify operation.
|
||||
|
||||
```js
|
||||
server.modify('o=example', (req, res, next) => {
|
||||
console.log('DN: ' + req.dn.toString());
|
||||
console.log('changes:');
|
||||
for (const c of req.changes) {
|
||||
console.log(' operation: ' + c.operation);
|
||||
console.log(' modification: ' + c.modification.toString());
|
||||
}
|
||||
res.end();
|
||||
});
|
||||
```
|
||||
|
||||
## ModifyRequest
|
||||
|
||||
ModifyRequest objects have the following properties:
|
||||
|
||||
### object
|
||||
|
||||
The DN the client is attempting to update (this is the same as the `dn`
|
||||
property).
|
||||
|
||||
### changes
|
||||
|
||||
An array of `Change` objects the client is attempting to perform. See below for
|
||||
details on the `Change` object.
|
||||
|
||||
## Change
|
||||
|
||||
The `Change` object will have the following properties:
|
||||
|
||||
### operation
|
||||
|
||||
A string, and will be one of: 'add', 'delete', or 'replace'.
|
||||
|
||||
### modification
|
||||
|
||||
Will be an `Attribute` object, which will have a 'type' (string) field, and
|
||||
'vals', which will be an array of string values.
|
||||
|
||||
## ModifyResponse
|
||||
|
||||
No extra methods above an `LDAPResult` API call.
|
||||
|
||||
# del
|
||||
|
||||
Allows you to handle an LDAP delete operation.
|
||||
|
||||
```js
|
||||
server.del('o=example', (req, res, next) => {
|
||||
console.log('DN: ' + req.dn.toString());
|
||||
res.end();
|
||||
});
|
||||
```
|
||||
|
||||
## DeleteRequest
|
||||
|
||||
### entry
|
||||
|
||||
The DN the client is attempting to delete (this is the same as the `dn`
|
||||
property).
|
||||
|
||||
## DeleteResponse
|
||||
|
||||
No extra methods above an `LDAPResult` API call.
|
||||
|
||||
# compare
|
||||
|
||||
Allows you to handle an LDAP compare operation.
|
||||
|
||||
```js
|
||||
server.compare('o=example', (req, res, next) => {
|
||||
console.log('DN: ' + req.dn.toString());
|
||||
console.log('attribute name: ' + req.attribute);
|
||||
console.log('attribute value: ' + req.value);
|
||||
res.end(req.value === 'foo');
|
||||
});
|
||||
```
|
||||
|
||||
## CompareRequest
|
||||
|
||||
### entry
|
||||
|
||||
The DN the client is attempting to compare (this is the same as the `dn`
|
||||
property).
|
||||
|
||||
### attribute
|
||||
|
||||
The string name of the attribute to compare values of.
|
||||
|
||||
### value
|
||||
|
||||
The string value of the attribute to compare.
|
||||
|
||||
## CompareResponse
|
||||
|
||||
The `end()` method for compare takes a boolean, as opposed to a numeric code
|
||||
(you can still pass in a numeric LDAP status code if you want). Beyond
|
||||
that, there are no extra methods above an `LDAPResult` API call.
|
||||
|
||||
# modifyDN
|
||||
|
||||
Allows you to handle an LDAP modifyDN operation.
|
||||
|
||||
```js
|
||||
server.modifyDN('o=example', (req, res, next) => {
|
||||
console.log('DN: ' + req.dn.toString());
|
||||
console.log('new RDN: ' + req.newRdn.toString());
|
||||
console.log('deleteOldRDN: ' + req.deleteOldRdn);
|
||||
console.log('new superior: ' +
|
||||
(req.newSuperior ? req.newSuperior.toString() : ''));
|
||||
|
||||
res.end();
|
||||
});
|
||||
```
|
||||
|
||||
## ModifyDNRequest
|
||||
|
||||
### entry
|
||||
|
||||
The DN the client is attempting to rename (this is the same as the `dn`
|
||||
property).
|
||||
|
||||
### newRdn
|
||||
|
||||
The leaf RDN the client wants to rename this entry to. This will be a DN object.
|
||||
|
||||
### deleteOldRdn
|
||||
|
||||
Whether or not to delete the old RDN (i.e., rename vs copy). Defaults to 'true'.
|
||||
|
||||
### newSuperior
|
||||
|
||||
Optional (DN). If the modifyDN operation wishes to relocate the entry in the
|
||||
tree, the `newSuperior` field will contain the new parent.
|
||||
|
||||
## ModifyDNResponse
|
||||
|
||||
No extra methods above an `LDAPResult` API call.
|
||||
|
||||
# exop
|
||||
|
||||
Allows you to handle an LDAP extended operation. Extended operations are pretty
|
||||
much arbitrary extensions, by definition. Typically the extended 'name' is an
|
||||
OID, but ldapjs makes no such restrictions; it just needs to be a string.
|
||||
Unlike the other operations, extended operations don't map to any location in
|
||||
the tree, so routing here will be exact match, as opposed to subtree.
|
||||
|
||||
```js
|
||||
// LDAP whoami
|
||||
server.exop('1.3.6.1.4.1.4203.1.11.3', (req, res, next) => {
|
||||
console.log('name: ' + req.name);
|
||||
console.log('value: ' + req.value);
|
||||
res.value = 'u:xxyyz@EXAMPLE.NET';
|
||||
res.end();
|
||||
return next();
|
||||
});
|
||||
```
|
||||
|
||||
## ExtendedRequest
|
||||
|
||||
### name
|
||||
|
||||
Will always be a match to the route-defined name. Clients must include this
|
||||
in their requests.
|
||||
|
||||
### value
|
||||
|
||||
Optional string. The arbitrary blob the client sends for this extended
|
||||
operation.
|
||||
|
||||
## ExtendedResponse
|
||||
|
||||
### name
|
||||
|
||||
The name of the extended operation. ldapjs will automatically set this.
|
||||
|
||||
### value
|
||||
|
||||
The arbitrary (string) value to send back as part of the response.
|
||||
|
||||
# unbind
|
||||
|
||||
ldapjs by default provides an unbind handler that just disconnects the client
|
||||
and cleans up any internals (in ldapjs core). You can override this handler
|
||||
if you need to clean up any items in your backend, or perform any other cleanup
|
||||
tasks you need to.
|
||||
|
||||
```js
|
||||
server.unbind((req, res, next) => {
|
||||
res.end();
|
||||
});
|
||||
```
|
||||
|
||||
Note that the LDAP unbind operation actually doesn't send any response (by
|
||||
definition in the RFC), so the UnbindResponse is really just a stub that
|
||||
ultimately calls `net.Socket.end()` for you. There are no properties available
|
||||
on either the request or response objects, except, of course, for `end()` on the
|
||||
response.
|
||||
65
node_modules/ldapjs/examples/cluster-threading-net-server.js
generated
vendored
Normal file
65
node_modules/ldapjs/examples/cluster-threading-net-server.js
generated
vendored
Normal 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
65
node_modules/ldapjs/examples/cluster-threading.js
generated
vendored
Normal 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
177
node_modules/ldapjs/examples/inmemory.js
generated
vendored
Normal 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
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
7
node_modules/ldapjs/lib/client/constants.js
generated
vendored
Normal 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
23
node_modules/ldapjs/lib/client/index.js
generated
vendored
Normal 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]'
|
||||
}
|
||||
25
node_modules/ldapjs/lib/client/message-tracker/ge-window.js
generated
vendored
Normal file
25
node_modules/ldapjs/lib/client/message-tracker/ge-window.js
generated
vendored
Normal 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))
|
||||
}
|
||||
}
|
||||
23
node_modules/ldapjs/lib/client/message-tracker/id-generator.js
generated
vendored
Normal file
23
node_modules/ldapjs/lib/client/message-tracker/id-generator.js
generated
vendored
Normal 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
161
node_modules/ldapjs/lib/client/message-tracker/index.js
generated
vendored
Normal 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
|
||||
}
|
||||
34
node_modules/ldapjs/lib/client/message-tracker/purge-abandoned.js
generated
vendored
Normal file
34
node_modules/ldapjs/lib/client/message-tracker/purge-abandoned.js
generated
vendored
Normal 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)
|
||||
})
|
||||
}
|
||||
36
node_modules/ldapjs/lib/client/request-queue/enqueue.js
generated
vendored
Normal file
36
node_modules/ldapjs/lib/client/request-queue/enqueue.js
generated
vendored
Normal 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
24
node_modules/ldapjs/lib/client/request-queue/flush.js
generated
vendored
Normal 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
39
node_modules/ldapjs/lib/client/request-queue/index.js
generated
vendored
Normal 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
12
node_modules/ldapjs/lib/client/request-queue/purge.js
generated
vendored
Normal 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
167
node_modules/ldapjs/lib/client/search_pager.js
generated
vendored
Normal 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
4
node_modules/ldapjs/lib/controls/index.js
generated
vendored
Normal 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
50
node_modules/ldapjs/lib/corked_emitter.js
generated
vendored
Normal 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
47
node_modules/ldapjs/lib/errors/codes.js
generated
vendored
Normal 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
147
node_modules/ldapjs/lib/errors/index.js
generated
vendored
Normal 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
84
node_modules/ldapjs/lib/index.js
generated
vendored
Normal 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
6
node_modules/ldapjs/lib/logger.js
generated
vendored
Normal 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
39
node_modules/ldapjs/lib/messages/index.js
generated
vendored
Normal 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
249
node_modules/ldapjs/lib/messages/parser.js
generated
vendored
Normal 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
122
node_modules/ldapjs/lib/messages/search_response.js
generated
vendored
Normal 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
109
node_modules/ldapjs/lib/persistent_search.js
generated
vendored
Normal 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
917
node_modules/ldapjs/lib/server.js
generated
vendored
Normal 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
72
node_modules/ldapjs/lib/url.js
generated
vendored
Normal 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
59
node_modules/ldapjs/package.json
generated
vendored
Normal 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
131
node_modules/ldapjs/scripts/build-docs.js
generated
vendored
Normal 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
5
node_modules/ldapjs/test-integration/.eslintrc.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
rules: {
|
||||
'no-shadow': 'off'
|
||||
}
|
||||
}
|
||||
21
node_modules/ldapjs/test-integration/client/connect.test.js
generated
vendored
Normal file
21
node_modules/ldapjs/test-integration/client/connect.test.js
generated
vendored
Normal 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()
|
||||
})
|
||||
})
|
||||
95
node_modules/ldapjs/test-integration/client/issue-860.test.js
generated
vendored
Normal file
95
node_modules/ldapjs/test-integration/client/issue-860.test.js
generated
vendored
Normal 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()
|
||||
})
|
||||
})
|
||||
})
|
||||
56
node_modules/ldapjs/test-integration/client/issue-883.test.js
generated
vendored
Normal file
56
node_modules/ldapjs/test-integration/client/issue-883.test.js
generated
vendored
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
55
node_modules/ldapjs/test-integration/client/issue-885.test.js
generated
vendored
Normal file
55
node_modules/ldapjs/test-integration/client/issue-885.test.js
generated
vendored
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
91
node_modules/ldapjs/test-integration/client/issue-923.test.js
generated
vendored
Normal file
91
node_modules/ldapjs/test-integration/client/issue-923.test.js
generated
vendored
Normal 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()
|
||||
})
|
||||
})
|
||||
}
|
||||
81
node_modules/ldapjs/test-integration/client/issue-940.test.js
generated
vendored
Normal file
81
node_modules/ldapjs/test-integration/client/issue-940.test.js
generated
vendored
Normal 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()
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
87
node_modules/ldapjs/test-integration/client/issue-946.test.js
generated
vendored
Normal file
87
node_modules/ldapjs/test-integration/client/issue-946.test.js
generated
vendored
Normal 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()
|
||||
})
|
||||
}
|
||||
})
|
||||
98
node_modules/ldapjs/test-integration/client/issues.test.js
generated
vendored
Normal file
98
node_modules/ldapjs/test-integration/client/issues.test.js
generated
vendored
Normal 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
9
node_modules/ldapjs/test/.eslintrc.js
generated
vendored
Normal 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
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
53
node_modules/ldapjs/test/controls/control.test.js
generated
vendored
Normal 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
104
node_modules/ldapjs/test/corked_emitter.test.js
generated
vendored
Normal 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
53
node_modules/ldapjs/test/errors.test.js
generated
vendored
Normal 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
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
116
node_modules/ldapjs/test/issue-845.test.js
generated
vendored
Normal 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
103
node_modules/ldapjs/test/issue-890.test.js
generated
vendored
Normal 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
143
node_modules/ldapjs/test/laundry.test.js
generated
vendored
Normal 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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
47
node_modules/ldapjs/test/lib/client/message-tracker/ge-window.test.js
generated
vendored
Normal file
47
node_modules/ldapjs/test/lib/client/message-tracker/ge-window.test.js
generated
vendored
Normal 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)
|
||||
})
|
||||
21
node_modules/ldapjs/test/lib/client/message-tracker/id-generator.test.js
generated
vendored
Normal file
21
node_modules/ldapjs/test/lib/client/message-tracker/id-generator.test.js
generated
vendored
Normal 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)
|
||||
})
|
||||
201
node_modules/ldapjs/test/lib/client/message-tracker/index.test.js
generated
vendored
Normal file
201
node_modules/ldapjs/test/lib/client/message-tracker/index.test.js
generated
vendored
Normal 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()
|
||||
})
|
||||
64
node_modules/ldapjs/test/lib/client/message-tracker/purge-abandoned.test.js
generated
vendored
Normal file
64
node_modules/ldapjs/test/lib/client/message-tracker/purge-abandoned.test.js
generated
vendored
Normal 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')
|
||||
}
|
||||
})
|
||||
82
node_modules/ldapjs/test/lib/client/request-queue/enqueue.test.js
generated
vendored
Normal file
82
node_modules/ldapjs/test/lib/client/request-queue/enqueue.test.js
generated
vendored
Normal 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)
|
||||
})
|
||||
51
node_modules/ldapjs/test/lib/client/request-queue/flush.test.js
generated
vendored
Normal file
51
node_modules/ldapjs/test/lib/client/request-queue/flush.test.js
generated
vendored
Normal 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 () {}
|
||||
})
|
||||
18
node_modules/ldapjs/test/lib/client/request-queue/purge.test.js
generated
vendored
Normal file
18
node_modules/ldapjs/test/lib/client/request-queue/purge.test.js
generated
vendored
Normal 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
16
node_modules/ldapjs/test/messages/parser.test.js
generated
vendored
Normal 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
471
node_modules/ldapjs/test/server.test.js
generated
vendored
Normal 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
58
node_modules/ldapjs/test/url.test.js
generated
vendored
Normal 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
22
node_modules/ldapjs/test/utils.js
generated
vendored
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user