Compare commits

...

70 Commits

Author SHA1 Message Date
James Sumners 8ffd0bc9c1
Add decommission note 2024-05-14 18:10:45 -04:00
James Sumners b86c493e7b
v3.0.7 2023-12-01 09:20:59 -05:00
Varun Patil 9c6142dbbf server: prevent crash on blank DN bind 2023-12-01 09:20:32 -05:00
James Sumners 6ceef13014
v3.0.6 2023-11-10 07:13:39 -05:00
its-sami a433489e98
Add integration test for PasswordPolicyControl (#949) 2023-11-10 07:12:57 -05:00
James Sumners bec2ff8e73 Add test for issue 940 2023-08-30 10:08:26 -04:00
James Sumners 6a676364ed
v3.0.5 2023-08-16 12:46:31 -04:00
James Sumners 971f1bbc3b Resolve issue #860 2023-08-16 12:45:52 -04:00
James Sumners 7b869f4a9d Resolve issue #924 2023-08-15 09:04:46 -04:00
James Sumners ac588a0fad Add integration test for issue #923 2023-08-04 12:36:59 -04:00
James Sumners 1cc6a733ba
v3.0.4 2023-07-22 15:43:21 -04:00
Niklas Mischkulnig 0fcad2443d
Fix ensureDN (#918) 2023-07-22 15:42:51 -04:00
James Sumners 3c7b7cbedf
v3.0.3 2023-07-04 08:57:20 -04:00
James Sumners 1fe60e48c3 Update minimum dependencies 2023-07-04 08:55:39 -04:00
James Sumners e2d516f6bb Address crash for unmatched server responses 2023-07-04 07:31:17 -04:00
Mihir Bhansali 70ce9c3643
update modification object in ldap.change (#910)
* updated the modification object in ldap.change

* Adding Change Interface

* Modification reference table
2023-06-28 07:30:30 -04:00
dependabot[bot] f2890088e4 build(deps-dev): bump eslint from 8.41.0 to 8.42.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.41.0 to 8.42.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.41.0...v8.42.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-03 07:23:07 -04:00
dependabot[bot] 6bd92a74ec build(deps-dev): bump eslint from 8.40.0 to 8.41.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.40.0 to 8.41.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.40.0...v8.41.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-20 08:22:07 -04:00
dependabot[bot] 830659ff93 build(deps-dev): bump eslint-plugin-n from 15.7.0 to 16.0.0
Bumps [eslint-plugin-n](https://github.com/eslint-community/eslint-plugin-n) from 15.7.0 to 16.0.0.
- [Release notes](https://github.com/eslint-community/eslint-plugin-n/releases)
- [Commits](https://github.com/eslint-community/eslint-plugin-n/compare/15.7.0...16.0.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-n
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-20 08:18:26 -04:00
James Sumners 0558c1a6cb Add test for issue #883 2023-05-18 08:32:23 -04:00
James Sumners bdaaf29d5a Add test for issue #885 2023-05-16 14:01:46 -04:00
dependabot[bot] a37daf168a build(deps-dev): bump eslint from 8.39.0 to 8.40.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.39.0 to 8.40.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.39.0...v8.40.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-06 15:29:16 -04:00
dependabot[bot] 29ddc4db06 build(deps): bump @ldapjs/dn from 1.0.0 to 1.1.0
Bumps [@ldapjs/dn](https://github.com/ldapjs/dn) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/ldapjs/dn/releases)
- [Commits](https://github.com/ldapjs/dn/compare/v1.0.0...v1.1.0)

---
updated-dependencies:
- dependency-name: "@ldapjs/dn"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-29 07:49:26 -04:00
dependabot[bot] 7fc99fd721 build(deps): bump @ldapjs/messages from 1.0.2 to 1.1.0
Bumps [@ldapjs/messages](https://github.com/ldapjs/messages) from 1.0.2 to 1.1.0.
- [Release notes](https://github.com/ldapjs/messages/releases)
- [Commits](https://github.com/ldapjs/messages/compare/v1.0.2...v1.1.0)

---
updated-dependencies:
- dependency-name: "@ldapjs/messages"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-22 07:37:29 -04:00
dependabot[bot] 2363ec77c0 build(deps-dev): bump eslint from 8.38.0 to 8.39.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.38.0 to 8.39.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.38.0...v8.39.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-22 07:29:45 -04:00
dependabot[bot] 2bb8985ada build(deps-dev): bump eslint from 8.37.0 to 8.38.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.37.0 to 8.38.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.37.0...v8.38.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-08 08:28:18 -04:00
dependabot[bot] 65a519e203 build(deps-dev): bump eslint from 8.36.0 to 8.37.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.36.0 to 8.37.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.36.0...v8.37.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-01 08:10:31 -04:00
James Sumners 49add89fa0
v3.0.2 2023-03-28 10:34:30 -04:00
Axel Kittenberger cfba105ecd cross-realm type checks in search 2023-03-28 10:34:04 -04:00
Axel Kittenberger 78014a2ea3 transcontextual safe type checks 2023-03-28 10:34:04 -04:00
Axel Kittenberger e2c0949dd5 transcontextual safe type checks 2023-03-28 10:34:04 -04:00
James Sumners 8c58f462fc Add test for issue 860
This PR adds a new integration test for issue #860.
2023-03-27 09:57:55 -04:00
dependabot[bot] 9613308c33 build(deps): bump @ldapjs/messages from 1.0.1 to 1.0.2
Bumps [@ldapjs/messages](https://github.com/ldapjs/messages) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/ldapjs/messages/releases)
- [Commits](https://github.com/ldapjs/messages/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: "@ldapjs/messages"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-11 08:55:04 -05:00
dependabot[bot] 7991a37c2c build(deps-dev): bump eslint from 8.35.0 to 8.36.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.35.0 to 8.36.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.35.0...v8.36.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-11 08:27:46 -05:00
James Sumners 5c6c5dce78
v3.0.1 2023-03-08 17:27:13 -05:00
James Sumners 98b91de01a Add Node.js version support to readme 2023-03-08 17:25:16 -05:00
James Sumners 824dd2cb57 Resolve issue #845
This issue resolves issue #845 by updating `@ldapjs/messages` to the
latest version and adding a test that shows how the module should be
used to evaluate search request scopes.
2023-03-08 17:11:44 -05:00
Joakim Uddholm 0dfe40e209 Quick fix for outdated client doc
Still referring to result.object, instead of result.pojo.
2023-03-07 16:09:06 -05:00
dependabot[bot] 6ba15ed672 build(deps-dev): bump eslint from 8.34.0 to 8.35.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.34.0 to 8.35.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.34.0...v8.35.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-04 08:14:42 -05:00
James Sumners 67617fee4f v3.0.0 2023-02-22 14:09:27 -05:00
James Sumners f248b4791e Change server.listen default address 2023-02-22 14:09:27 -05:00
James Sumners 547ceb6fa1 Update deps 2023-02-22 14:09:27 -05:00
James Sumners 0ed048233e Fix Set size querying 2023-02-22 14:09:27 -05:00
James Sumners cc042f9a27 Remove dtrace 2023-02-22 14:09:27 -05:00
James Sumners b08f8e9e5f Update supported Node versions (CI) 2023-02-22 14:09:27 -05:00
James Sumners 0a3702e1d0 Fix search attributes 2023-02-22 14:09:27 -05:00
James Sumners 9038aeb73e Reduce coverage requirement 2023-02-22 14:09:27 -05:00
James Sumners f18dee40a2 Replace messages with @ldapjs/messages 2023-02-22 14:09:27 -05:00
Joakim Uddholm 685465843d document correct connection error in client documentation 2023-02-22 14:09:27 -05:00
James Sumners 9de9c703ab Migrate filter extensions (#809)
* Replace presence filter

* Replace equality filter

* Remove TODO

* Fix integration tests

* Replace approximate filter

* Replace extensible filter

* Replace greater-than-equals filter

* Replace less-than-equals filter

* Replace remaining filters

* Remove debug code

* Remove transition code

* Remove unnecessry isFilter

* Remove unused code

* Use LDAP filter string parsing from @ldapjs/filter

* Move BER filter parsing to @ldapjs/filter

* Fully replace internal filters module with @ldapjs/filter
2023-02-22 14:09:27 -05:00
James Sumners 5bab39f58e Replace node-filter (resolves #622) (#808) 2023-02-22 14:09:27 -05:00
James Sumners cb70776445 Replace protocol with module (#806) 2023-02-22 14:09:27 -05:00
James Sumners 4355893077 Replace internal controls with @ldapjs/controls (#797)
* Replace internal controls with @ldapjs/controls

* Replace EntryChangeNotificationControl

* Replace PagedResultsControl

* Replace ServerSideSortingRequestControl

* Replace ServerSideSortingResponseControl

* Replace VLV controls

* Reduce coverage requirement

* Fix dependency qualifier
2023-02-22 14:09:27 -05:00
James Sumners 9bd8761ea3 Remove coveralls 2023-02-22 14:09:27 -05:00
James Sumners caab2c2b6f Update dependencies and CI (#796)
* Update dependencies

* Run CI for "next" releases

* Grrr

* Update target versions

* Fix test on Node 17

* Fix deprecation notice
2023-02-22 14:09:27 -05:00
James Sumners 8dce600849 Use asn1@1.0.0 2023-02-22 14:09:27 -05:00
James Sumners 9fe58a44a2 Swap asn1 package 2023-02-22 14:09:27 -05:00
James Sumners 92dfc80fd1
v2.3.3 (#807) 2022-06-07 21:06:42 -04:00
Max Leiter 188870d7b5
Progress on supporting IPv6 (#805)
* Progress on supporting IPv6

* Apply suggestions from code review

Co-authored-by: James Sumners <james@sumners.email>

* tests: add IPv6 URL format test to server.test.js

Co-authored-by: James Sumners <james@sumners.email>
2022-06-07 20:59:33 -04:00
James Sumners 9143456b4f
Merge pull request #794 from ldapjs/docker-updates
Update docker config
2022-03-27 12:06:33 -04:00
James Sumners 7a758f27ad
Update docker config 2022-03-27 12:02:59 -04:00
James Sumners fd39f3bdc0
Merge pull request #792 from ldapjs/dependabot/npm_and_yarn/tap-15.2.3
build(deps-dev): bump tap from 15.2.2 to 15.2.3
2022-03-19 09:26:07 -04:00
dependabot[bot] 003654ee37
build(deps-dev): bump tap from 15.2.2 to 15.2.3
Bumps [tap](https://github.com/tapjs/node-tap) from 15.2.2 to 15.2.3.
- [Release notes](https://github.com/tapjs/node-tap/releases)
- [Commits](https://github.com/tapjs/node-tap/compare/v15.2.2...v15.2.3)

---
updated-dependencies:
- dependency-name: tap
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-19 07:01:57 +00:00
Tony Brix 91172e3929
Merge pull request #790 from ldapjs/dependabot/npm_and_yarn/tap-15.2.2
build(deps-dev): bump tap from 15.1.6 to 15.2.2
2022-03-05 08:54:24 -06:00
Tony Brix dbe5836063
Merge branch 'master' into dependabot/npm_and_yarn/tap-15.2.2 2022-03-05 08:47:12 -06:00
Tony Brix d8cb593ce4
Merge pull request #789 from ldapjs/dependabot/github_actions/actions/checkout-3
build(deps): bump actions/checkout from 2 to 3
2022-03-05 07:13:31 -06:00
dependabot[bot] dbf9dbcda8
build(deps-dev): bump tap from 15.1.6 to 15.2.2
Bumps [tap](https://github.com/tapjs/node-tap) from 15.1.6 to 15.2.2.
- [Release notes](https://github.com/tapjs/node-tap/releases)
- [Commits](https://github.com/tapjs/node-tap/compare/v15.1.6...v15.2.2)

---
updated-dependencies:
- dependency-name: tap
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-05 08:05:26 +00:00
dependabot[bot] de5a3797d8
build(deps): bump actions/checkout from 2 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-05 08:02:23 +00:00
Tony Brix df2d212f20
Merge pull request #788 from ldapjs/dependabot/github_actions/actions/setup-node-3
build(deps): bump actions/setup-node from 2.5.1 to 3
2022-02-26 09:49:05 -06:00
dependabot[bot] f94640b692
build(deps): bump actions/setup-node from 2.5.1 to 3
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2.5.1 to 3.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2.5.1...v3)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-26 08:01:10 +00:00
135 changed files with 1580 additions and 8829 deletions

View File

@ -4,6 +4,9 @@ module.exports = {
es2021: true,
node: true
},
parserOptions: {
ecmaVersion: 'latest'
},
extends: [
'standard'
],

View File

@ -10,10 +10,10 @@ jobs:
name: Update Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2.5.1
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '14'
node-version: '18'
- name: Install Packages
run: npm install
- name: Build Docs

View File

@ -4,35 +4,34 @@ name: 'Integration Tests'
# 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: docker.pkg.github.com/ldapjs/docker-test-openldap/openldap:1.0
# ports:
# - 389:389
# - 636:636
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@v2
- uses: actions/setup-node@v2.5.1
# Hack way to start service since GitHub doesn't integrate with its own services
- name: Docker login
run: docker login docker.pkg.github.com -u ${GITHUB_ACTOR} -p ${GITHUB_TOKEN}
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
- name: Pull Docker image
run: docker pull "docker.pkg.github.com/ldapjs/docker-test-openldap/openldap:latest"
- name: Start OpenLDAP service
run: docker run -it -d --name openldap -p 389:389 -p 636:636 docker.pkg.github.com/ldapjs/docker-test-openldap/openldap:latest
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 'lts/*'
- name: Install Packages
run: npm install

View File

@ -4,17 +4,21 @@ on:
push:
branches:
- master
- next
pull_request:
branches:
- master
- next
jobs:
lint:
name: Lint Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2.5.1
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 'lts/*'
- name: Install Packages
run: npm install
- name: Lint Code
@ -28,27 +32,16 @@ jobs:
- ubuntu-latest
- windows-latest
node:
- 10.13.0
- 10.x
- 12.x
- 14.x
- 16
- 18
- 20
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2.5.1
- 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
- name: Coveralls Parallel
uses: coverallsapp/github-action@1.1.3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel: true
- name: Coveralls Finished
uses: coverallsapp/github-action@1.1.3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true

3
.npmrc Normal file
View File

@ -0,0 +1,3 @@
# npm general settings
package-lock=false
legacy-peer-deps=true

4
.taprc
View File

@ -1,4 +0,0 @@
check-coverage: false
files:
- 'test/**/*.test.js'

10
.taprc.yml Normal file
View File

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

View File

@ -1,54 +1,34 @@
# LDAPjs
# Project Decomissioned
[![Build Status](https://github.com/ldapjs/node-ldapjs/workflows/Lint%20And%20Test/badge.svg)](https://github.com/ldapjs/node-ldapjs/actions)
[![Coverage Status](https://coveralls.io/repos/github/ldapjs/node-ldapjs/badge.svg)](https://coveralls.io/github/ldapjs/node-ldapjs/)
This project has been decomissioned. I, James Sumners, took it on when it was
languishing without any maintenance as it filled a need in the ecosystem and
I had built things at a prior organization that depended upon this project.
I spent a lot of time triaging issues and reworking things toward a path
that could be more easily maintained by a community of volunteers. But I have
not had the time to dedicate to this project in quite a while. There are
outstanding issues that would take me at least a week of dedicated development
time to solve, and I cannot afford to take time off of work to do that.
Particularly considering that the aforementioned organization was two
jobs ago, and it is extremely unlikely that I will transition to a role again
that will need this project.
LDAPjs makes the LDAP protocol a first class citizen in Node.js.
So, why am I just now deciding to decomission this project? Because today,
2024-05-14, I received the following email:
## Usage
![Abusive email](dt.png)
For full docs, head on over to <http://ldapjs.org>.
I will not tolerate abuse, and I especially will not tolerate tacit death
threats, over a hobby. You can thank the author of that email for the
decomissioning on this project.
```javascript
var ldap = require('ldapjs');
My recommendation to you in regard to LDAP operations: write a gateway in a
language that is more suited to these types of operations. I'd suggest
[Go](https://go.dev).
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
DTrace support is included in ldapjs. To enable it, `npm install dtrace-provider`.
## License
MIT.
## Bugs
See <https://github.com/ldapjs/node-ldapjs/issues>.
P.S.: if I ever do need this project again, I might revive it. But I'd fight
hard for my suggestion above. Also, I will consider turning it over to an
interested party, but I will require at least one recommendation from a
Node.js core contributor that I can vet with the people that I know on that
team.

View File

@ -1,8 +1,10 @@
version: '3'
services:
openldap:
image: docker.pkg.github.com/ldapjs/docker-test-openldap/openldap:latest
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

View File

@ -22,7 +22,7 @@ const client = ldap.createClient({
url: ['ldap://127.0.0.1:1389', 'ldap://127.0.0.2:1389']
});
client.on('error', (err) => {
client.on('connectError', (err) => {
// handle connection error
})
```
@ -41,7 +41,6 @@ client is:
|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|
|strictDN |Force strict DN parsing for client methods (Default is true)|
|reconnect |Try to reconnect when the connection gets lost (Default is false)|
### url
@ -214,7 +213,8 @@ Example:
const change = new ldap.Change({
operation: 'add',
modification: {
pets: ['cat', 'dog']
type: 'pets',
values: ['cat', 'dog']
}
});
@ -235,7 +235,13 @@ must be one of:
| 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 values you want.
`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)`
@ -287,7 +293,7 @@ Responses inside callback of the `search` method are an `EventEmitter` where you
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
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
@ -306,10 +312,10 @@ client.search('o=example', opts, (err, res) => {
assert.ifError(err);
res.on('searchRequest', (searchRequest) => {
console.log('searchRequest: ', searchRequest.messageID);
console.log('searchRequest: ', searchRequest.messageId);
});
res.on('searchEntry', (entry) => {
console.log('entry: ' + JSON.stringify(entry.object));
console.log('entry: ' + JSON.stringify(entry.pojo));
});
res.on('searchReference', (referral) => {
console.log('referral: ' + referral.uris.join());

View File

@ -80,8 +80,15 @@ Example:
`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 any IPv4 address
(INADDR\_ANY).
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.
@ -197,7 +204,7 @@ 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).
identify the request/connection pair in logs (includes the LDAP messageId).
## Common Response Elements

BIN
dt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

View File

@ -130,7 +130,7 @@ server.search(SUFFIX, authorize, function (req, res, next) {
case 'base':
if (req.filter.matches(db[dn])) {
res.send({
dn: dn,
dn,
attributes: db[dn]
})
}

View File

@ -1,24 +0,0 @@
#!/usr/sbin/dtrace -s
#pragma D option quiet
BEGIN
{
printf("%-8s %-8s %-16s %-15s %-15s %s\n",
"LATENCY", "OPTYPE", "REMOTE IP", "BIND DN", "REQ DN",
"STATUS");
}
ldapjs*:::server-*-start
{
starts[arg0] = timestamp;
}
ldapjs*:::server-*-done
/starts[arg0]/
{
printf("%6dms %-8s %-16s %-15s %-15s %d\n",
(timestamp - starts[arg0]) / 1000000, strtok(probename + 7, "-"),
copyinstr(arg1), copyinstr(arg2), copyinstr(arg3), arg4);
starts[arg0] = 0;
}

View File

@ -1,54 +0,0 @@
// Copyright 2015 Joyent, Inc.
const assert = require('assert')
const util = require('util')
const isDN = require('./dn').DN.isDN
const isAttribute = require('./attribute').isAttribute
/// --- Helpers
// Copied from mcavage/node-assert-plus
function _assert (arg, type, name) {
name = name || type
throw new assert.AssertionError({
message: util.format('%s (%s) required', name, type),
actual: typeof (arg),
expected: type,
operator: '===',
stackStartFunction: _assert.caller
})
}
/// --- API
function stringDN (input, name) {
if (isDN(input) || typeof (input) === 'string') { return }
_assert(input, 'DN or string', name)
}
function optionalStringDN (input, name) {
if (input === undefined || isDN(input) || typeof (input) === 'string') { return }
_assert(input, 'DN or string', name)
}
function optionalDN (input, name) {
if (input !== undefined && !isDN(input)) { _assert(input, 'DN', name) }
}
function optionalArrayOfAttribute (input, name) {
if (input === undefined) { return }
if (!Array.isArray(input) ||
input.some(function (v) { return !isAttribute(v) })) {
_assert(input, 'array of Attribute', name)
}
}
/// --- Exports
module.exports = {
stringDN: stringDN,
optionalStringDN: optionalStringDN,
optionalDN: optionalDN,
optionalArrayOfAttribute: optionalArrayOfAttribute
}

View File

@ -1,160 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert')
const asn1 = require('asn1')
const Protocol = require('./protocol')
/// --- API
function Attribute (options) {
if (options) {
if (typeof (options) !== 'object') { throw new TypeError('options must be an object') }
if (options.type && typeof (options.type) !== 'string') { throw new TypeError('options.type must be a string') }
} else {
options = {}
}
this.type = options.type || ''
this._vals = []
if (options.vals !== undefined && options.vals !== null) { this.vals = options.vals }
}
module.exports = Attribute
Object.defineProperties(Attribute.prototype, {
buffers: {
get: function getBuffers () {
return this._vals
},
configurable: false
},
json: {
get: function getJson () {
return {
type: this.type,
vals: this.vals
}
},
configurable: false
},
vals: {
get: function getVals () {
const eType = _bufferEncoding(this.type)
return this._vals.map(function (v) {
return v.toString(eType)
})
},
set: function setVals (vals) {
const self = this
this._vals = []
if (Array.isArray(vals)) {
vals.forEach(function (v) {
self.addValue(v)
})
} else {
self.addValue(vals)
}
},
configurable: false
}
})
Attribute.prototype.addValue = function addValue (val) {
if (Buffer.isBuffer(val)) {
this._vals.push(val)
} else {
this._vals.push(Buffer.from(val + '', _bufferEncoding(this.type)))
}
}
/* BEGIN JSSTYLED */
Attribute.compare = function compare (a, b) {
if (!(Attribute.isAttribute(a)) || !(Attribute.isAttribute(b))) {
throw new TypeError('can only compare Attributes')
}
if (a.type < b.type) return -1
if (a.type > b.type) return 1
if (a.vals.length < b.vals.length) return -1
if (a.vals.length > b.vals.length) return 1
for (let i = 0; i < a.vals.length; i++) {
if (a.vals[i] < b.vals[i]) return -1
if (a.vals[i] > b.vals[i]) return 1
}
return 0
}
/* END JSSTYLED */
Attribute.prototype.parse = function parse (ber) {
assert.ok(ber)
ber.readSequence()
this.type = ber.readString()
if (ber.peek() === Protocol.LBER_SET) {
if (ber.readSequence(Protocol.LBER_SET)) {
const end = ber.offset + ber.length
while (ber.offset < end) { this._vals.push(ber.readString(asn1.Ber.OctetString, true)) }
}
}
return true
}
Attribute.prototype.toBer = function toBer (ber) {
assert.ok(ber)
ber.startSequence()
ber.writeString(this.type)
ber.startSequence(Protocol.LBER_SET)
if (this._vals.length) {
this._vals.forEach(function (b) {
ber.writeByte(asn1.Ber.OctetString)
ber.writeLength(b.length)
for (let i = 0; i < b.length; i++) { ber.writeByte(b[i]) }
})
} else {
ber.writeStringArray([])
}
ber.endSequence()
ber.endSequence()
return ber
}
Attribute.prototype.toString = function () {
return JSON.stringify(this.json)
}
Attribute.toBer = function (attr, ber) {
return Attribute.prototype.toBer.call(attr, ber)
}
Attribute.isAttribute = function isAttribute (attr) {
if (!attr || typeof (attr) !== 'object') {
return false
}
if (attr instanceof Attribute) {
return true
}
if ((typeof (attr.toBer) === 'function') &&
(typeof (attr.type) === 'string') &&
(Array.isArray(attr.vals)) &&
(attr.vals.filter(function (item) {
return (typeof (item) === 'string' ||
Buffer.isBuffer(item))
}).length === attr.vals.length)) {
return true
}
return false
}
function _bufferEncoding (type) {
/* JSSTYLED */
return /;binary$/.test(type) ? 'base64' : 'utf8'
}

View File

@ -1,213 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const Attribute = require('./attribute')
// var Protocol = require('./protocol')
/// --- API
function Change (options) {
if (options) {
assert.object(options)
assert.optionalString(options.operation)
} else {
options = {}
}
this._modification = false
this.operation = options.operation || options.type || 'add'
this.modification = options.modification || {}
}
Object.defineProperties(Change.prototype, {
operation: {
get: function getOperation () {
switch (this._operation) {
case 0x00: return 'add'
case 0x01: return 'delete'
case 0x02: return 'replace'
default:
throw new Error('0x' + this._operation.toString(16) + ' is invalid')
}
},
set: function setOperation (val) {
assert.string(val)
switch (val.toLowerCase()) {
case 'add':
this._operation = 0x00
break
case 'delete':
this._operation = 0x01
break
case 'replace':
this._operation = 0x02
break
default:
throw new Error('Invalid operation type: 0x' + val.toString(16))
}
},
configurable: false
},
modification: {
get: function getModification () {
return this._modification
},
set: function setModification (val) {
if (Attribute.isAttribute(val)) {
this._modification = val
return
}
// Does it have an attribute-like structure
if (Object.keys(val).length === 2 &&
typeof (val.type) === 'string' &&
Array.isArray(val.vals)) {
this._modification = new Attribute({
type: val.type,
vals: val.vals
})
return
}
const keys = Object.keys(val)
if (keys.length > 1) {
throw new Error('Only one attribute per Change allowed')
} else if (keys.length === 0) {
return
}
const k = keys[0]
const _attr = new Attribute({ type: k })
if (Array.isArray(val[k])) {
val[k].forEach(function (v) {
_attr.addValue(v.toString())
})
} else if (Buffer.isBuffer(val[k])) {
_attr.addValue(val[k])
} else if (val[k] !== undefined && val[k] !== null) {
_attr.addValue(val[k].toString())
}
this._modification = _attr
},
configurable: false
},
json: {
get: function getJSON () {
return {
operation: this.operation,
modification: this._modification ? this._modification.json : {}
}
},
configurable: false
}
})
Change.isChange = function isChange (change) {
if (!change || typeof (change) !== 'object') {
return false
}
if ((change instanceof Change) ||
((typeof (change.toBer) === 'function') &&
(change.modification !== undefined) &&
(change.operation !== undefined))) {
return true
}
return false
}
Change.compare = function (a, b) {
if (!Change.isChange(a) || !Change.isChange(b)) { throw new TypeError('can only compare Changes') }
if (a.operation < b.operation) { return -1 }
if (a.operation > b.operation) { return 1 }
return Attribute.compare(a.modification, b.modification)
}
/**
* Apply a Change to properties of an object.
*
* @param {Object} change the change to apply.
* @param {Object} obj the object to apply it to.
* @param {Boolean} scalar convert single-item arrays to scalars. Default: false
*/
Change.apply = function apply (change, obj, scalar) {
assert.string(change.operation)
assert.string(change.modification.type)
assert.ok(Array.isArray(change.modification.vals))
assert.object(obj)
const type = change.modification.type
const vals = change.modification.vals
let data = obj[type]
if (data !== undefined) {
if (!Array.isArray(data)) {
data = [data]
}
} else {
data = []
}
switch (change.operation) {
case 'replace':
if (vals.length === 0) {
// replace empty is a delete
delete obj[type]
return obj
} else {
data = vals
}
break
case 'add': {
// add only new unique entries
const newValues = vals.filter(function (entry) {
return (data.indexOf(entry) === -1)
})
data = data.concat(newValues)
break
}
case 'delete':
data = data.filter(function (entry) {
return (vals.indexOf(entry) === -1)
})
if (data.length === 0) {
// Erase the attribute if empty
delete obj[type]
return obj
}
break
default:
break
}
if (scalar && data.length === 1) {
// store single-value outputs as scalars, if requested
obj[type] = data[0]
} else {
obj[type] = data
}
return obj
}
Change.prototype.parse = function (ber) {
assert.ok(ber)
ber.readSequence()
this._operation = ber.readEnumeration()
this._modification = new Attribute()
this._modification.parse(ber)
return true
}
Change.prototype.toBer = function (ber) {
assert.ok(ber)
ber.startSequence()
ber.writeEnumeration(this._operation)
ber = this._modification.toBer(ber)
ber.endSequence()
return ber
}
/// --- Exports
module.exports = Change

View File

@ -15,37 +15,37 @@ const vasync = require('vasync')
const assert = require('assert-plus')
const VError = require('verror').VError
const Attribute = require('../attribute')
const Change = require('../change')
const Attribute = require('@ldapjs/attribute')
const Change = require('@ldapjs/change')
const Control = require('../controls/index').Control
const { Control: LdapControl } = require('@ldapjs/controls')
const SearchPager = require('./search_pager')
const Protocol = require('../protocol')
const dn = require('../dn')
const Protocol = require('@ldapjs/protocol')
const { DN } = require('@ldapjs/dn')
const errors = require('../errors')
const filters = require('../filters')
const messages = require('../messages')
const filters = require('@ldapjs/filter')
const Parser = require('../messages/parser')
const url = require('../url')
const CorkedEmitter = require('../corked_emitter')
/// --- Globals
const AbandonRequest = messages.AbandonRequest
const AddRequest = messages.AddRequest
const BindRequest = messages.BindRequest
const CompareRequest = messages.CompareRequest
const DeleteRequest = messages.DeleteRequest
const ExtendedRequest = messages.ExtendedRequest
const ModifyRequest = messages.ModifyRequest
const ModifyDNRequest = messages.ModifyDNRequest
const SearchRequest = messages.SearchRequest
const UnbindRequest = messages.UnbindRequest
const UnbindResponse = messages.UnbindResponse
const LDAPResult = messages.LDAPResult
const SearchEntry = messages.SearchEntry
const SearchReference = messages.SearchReference
// var SearchResponse = messages.SearchResponse
const Parser = messages.Parser
const messages = require('@ldapjs/messages')
const {
AbandonRequest,
AddRequest,
BindRequest,
CompareRequest,
DeleteRequest,
ExtensionRequest: ExtendedRequest,
ModifyRequest,
ModifyDnRequest: ModifyDNRequest,
SearchRequest,
UnbindRequest,
LdapResult: LDAPResult,
SearchResultEntry: SearchEntry,
SearchResultReference: SearchReference
} = messages
const PresenceFilter = filters.PresenceFilter
@ -67,9 +67,9 @@ function nextClientId () {
function validateControls (controls) {
if (Array.isArray(controls)) {
controls.forEach(function (c) {
if (!(c instanceof Control)) { throw new TypeError('controls must be [Control]') }
if (!(c instanceof Control) && !(c instanceof LdapControl)) { throw new TypeError('controls must be [Control]') }
})
} else if (controls instanceof Control) {
} else if (controls instanceof Control || controls instanceof LdapControl) {
controls = [controls]
} else {
throw new TypeError('controls must be [Control]')
@ -78,13 +78,11 @@ function validateControls (controls) {
return controls
}
function ensureDN (input, strict) {
if (dn.DN.isDN(input)) {
return dn
} else if (strict) {
return dn.parse(input)
} else if (typeof (input) === 'string') {
function ensureDN (input) {
if (DN.isDn(input)) {
return input
} else if (typeof (input) === 'string') {
return DN.fromString(input)
} else {
throw new Error('invalid DN')
}
@ -136,7 +134,6 @@ function Client (options) {
failAfter: parseInt(rOpts.failAfter, 10) || Infinity
}
}
this.strictDN = (options.strictDN !== undefined) ? options.strictDN : true
this.queue = requestQueueFactory({
size: parseInt((options.queueSize || 0), 10),
@ -177,13 +174,13 @@ module.exports = Client
* The callback will be invoked as soon as the data is flushed out to the
* network, as there is never a response from abandon.
*
* @param {Number} messageID the messageID to abandon.
* @param {Number} messageId the messageId to abandon.
* @param {Control} controls (optional) either a Control or [Control].
* @param {Function} callback of the form f(err).
* @throws {TypeError} on invalid input.
*/
Client.prototype.abandon = function abandon (messageID, controls, callback) {
assert.number(messageID, 'messageID')
Client.prototype.abandon = function abandon (messageId, controls, callback) {
assert.number(messageId, 'messageId')
if (typeof (controls) === 'function') {
callback = controls
controls = []
@ -193,8 +190,8 @@ Client.prototype.abandon = function abandon (messageID, controls, callback) {
assert.func(callback, 'callback')
const req = new AbandonRequest({
abandonID: messageID,
controls: controls
abandonId: messageId,
controls
})
return this._send(req, 'abandon', null, callback)
@ -248,9 +245,9 @@ Client.prototype.add = function add (name, entry, controls, callback) {
}
const req = new AddRequest({
entry: ensureDN(name, this.strictDN),
entry: ensureDN(name),
attributes: entry,
controls: controls
controls
})
return this._send(req, [errors.LDAP_SUCCESS], null, callback)
@ -270,7 +267,12 @@ Client.prototype.bind = function bind (name,
controls,
callback,
_bypass) {
if (typeof (name) !== 'string' && !(name instanceof dn.DN)) { throw new TypeError('name (string) required') }
if (
typeof (name) !== 'string' &&
Object.prototype.toString.call(name) !== '[object LdapDn]'
) {
throw new TypeError('name (string) required')
}
assert.optionalString(credentials, 'credentials')
if (typeof (controls) === 'function') {
callback = controls
@ -284,7 +286,7 @@ Client.prototype.bind = function bind (name,
name: name || '',
authentication: 'Simple',
credentials: credentials || '',
controls: controls
controls
})
// Connection errors will be reported to the bind callback too (useful when the LDAP server is not available)
@ -325,10 +327,10 @@ Client.prototype.compare = function compare (name,
assert.func(callback, 'callback')
const req = new CompareRequest({
entry: ensureDN(name, this.strictDN),
entry: ensureDN(name),
attribute: attr,
value: value,
controls: controls
value,
controls
})
return this._send(req, CMP_EXPECT, null, function (err, res) {
@ -357,8 +359,8 @@ Client.prototype.del = function del (name, controls, callback) {
assert.func(callback, 'callback')
const req = new DeleteRequest({
entry: ensureDN(name, this.strictDN),
controls: controls
entry: ensureDN(name),
controls
})
return this._send(req, [errors.LDAP_SUCCESS], null, callback)
@ -395,7 +397,7 @@ Client.prototype.exop = function exop (name, value, controls, callback) {
const req = new ExtendedRequest({
requestName: name,
requestValue: value,
controls: controls
controls
})
return this._send(req, [errors.LDAP_SUCCESS], null, function (err, res) {
@ -468,9 +470,9 @@ Client.prototype.modify = function modify (name, change, controls, callback) {
assert.func(callback, 'callback')
const req = new ModifyRequest({
object: ensureDN(name, this.strictDN),
changes: changes,
controls: controls
object: ensureDN(name),
changes,
controls
})
return this._send(req, [errors.LDAP_SUCCESS], null, callback)
@ -504,18 +506,16 @@ Client.prototype.modifyDN = function modifyDN (name,
}
assert.func(callback)
const DN = ensureDN(name)
// TODO: is non-strict handling desired here?
const newDN = dn.parse(newName)
const newDN = DN.fromString(newName)
const req = new ModifyDNRequest({
entry: DN,
entry: DN.fromString(name),
deleteOldRdn: true,
controls: controls
controls
})
if (newDN.length !== 1) {
req.newRdn = dn.parse(newDN.rdns.shift().toString())
req.newRdn = DN.fromString(newDN.shift().toString())
req.newSuperior = newDN
} else {
req.newRdn = newDN
@ -571,7 +571,7 @@ Client.prototype.search = function search (base,
options.filter = filters.parseString(options.filter)
} else if (!options.filter) {
options.filter = new PresenceFilter({ attribute: 'objectclass' })
} else if (!filters.isFilter(options.filter)) {
} else if (Object.prototype.toString.call(options.filter) !== '[object FilterString]') {
throw new TypeError('options.filter (Filter) required')
}
if (typeof (controls) === 'function') {
@ -593,14 +593,14 @@ Client.prototype.search = function search (base,
}
const self = this
const baseDN = ensureDN(base, this.strictDN)
const baseDN = ensureDN(base)
function sendRequest (ctrls, emitter, cb) {
const req = new SearchRequest({
baseObject: baseDN,
scope: options.scope || 'base',
filter: options.filter,
derefAliases: options.derefAliases || Protocol.NEVER_DEREF_ALIASES,
derefAliases: options.derefAliases || Protocol.search.NEVER_DEREF_ALIASES,
sizeLimit: options.sizeLimit || 0,
timeLimit: options.timeLimit || 10,
typesOnly: options.typesOnly || false,
@ -629,11 +629,11 @@ Client.prototype.search = function search (base,
}
const pager = new SearchPager({
callback: callback,
controls: controls,
callback,
controls,
pageSize: size,
pagePause: pageOpts.pagePause,
sendRequest: sendRequest
sendRequest
})
pager.begin()
} else {
@ -723,7 +723,7 @@ Client.prototype.starttls = function starttls (options,
self._tracker.parser.write(data)
})
secure.on('error', function (err) {
self.log.trace({ err: err }, 'error event: %s', new Error().stack)
self.log.trace({ err }, 'error event: %s', new Error().stack)
self.emit('error', err)
sock.destroy()
@ -744,7 +744,7 @@ Client.prototype.starttls = function starttls (options,
const req = new ExtendedRequest({
requestName: '1.3.6.1.4.1.1466.20037',
requestValue: null,
controls: controls
controls
})
return this._send(req,
@ -857,7 +857,7 @@ Client.prototype.connect = function connect () {
function initSocket (server) {
tracker = messageTrackerFactory({
id: server ? server.href : self.socketPath,
parser: new Parser({ log: log })
parser: new Parser({ log })
})
// This won't be set on TLS. So. Very. Annoying.
@ -876,15 +876,52 @@ Client.prototype.connect = function connect () {
})
// The "router"
//
// This is invoked after the incoming BER has been parsed into a JavaScript
// object.
tracker.parser.on('message', function onMessage (message) {
message.connection = self._socket
const callback = tracker.fetch(message.messageID)
const trackedObject = tracker.fetch(message.messageId)
if (!trackedObject) {
log.error({ message: message.pojo }, 'unmatched server message received')
return false
}
const { message: trackedMessage, callback } = trackedObject
if (!callback) {
log.error({ message: message.json }, 'unsolicited message')
log.error({ message: message.pojo }, 'unsolicited message')
return false
}
// Some message types have narrower implementations and require extra
// parsing to be complete. In particular, ExtensionRequest messages will
// return responses that do not identify the request that generated them.
// Therefore, we have to match the response to the request and handle
// the extra processing accordingly.
switch (trackedMessage.type) {
case 'ExtensionRequest': {
const extensionType = ExtendedRequest.recognizedOIDs().lookupName(trackedMessage.requestName)
switch (extensionType) {
case 'PASSWORD_MODIFY': {
message = messages.PasswordModifyResponse.fromResponse(message)
break
}
case 'WHO_AM_I': {
message = messages.WhoAmIResponse.fromResponse(message)
break
}
default:
}
break
}
default:
}
return callback(message)
})
@ -965,7 +1002,7 @@ Client.prototype.connect = function connect () {
socket.end()
})
socket.on('error', function onSocketError (err) {
log.trace({ err: err }, 'error event: %s', new Error().stack)
log.trace({ err }, 'error event: %s', new Error().stack)
self.emit('error', err)
socket.destroy()
@ -1083,8 +1120,16 @@ Client.prototype._onClose = function _onClose (closeError) {
return cb(new ConnectionError(tracker.id + ' closed'))
} else {
// Unbinds will be communicated as a success since we're closed
const unbind = new UnbindResponse({ messageID: msgid })
unbind.status = 'unbind'
// TODO: we are faking this "UnbindResponse" object in order to make
// tests pass. There is no such thing as an "unbind response" in the LDAP
// protocol. When the client is revamped, this logic should be removed.
// ~ jsumners 2023-02-16
const Unbind = class extends LDAPResult {
messageID = msgid
messageId = msgid
status = 'unbind'
}
const unbind = new Unbind()
return cb(unbind)
}
})
@ -1202,21 +1247,23 @@ Client.prototype._sendSocket = function _sendSocket (message,
function messageCallback (msg) {
if (timer) { clearTimeout(timer) }
log.trace({ msg: msg ? msg.json : null }, 'response received')
log.trace({ msg: msg ? msg.pojo : null }, 'response received')
if (expect === 'abandon') { return sendResult('end', null) }
if (msg instanceof SearchEntry || msg instanceof SearchReference) {
let event = msg.constructor.name
event = event[0].toLowerCase() + event.slice(1)
// Generate the event name for the event emitter, i.e. "searchEntry"
// and "searchReference".
event = (event[0].toLowerCase() + event.slice(1)).replaceAll('Result', '')
return sendResult(event, msg)
} else {
tracker.remove(message.messageID)
tracker.remove(message.messageId)
// Potentially mark client as idle
self._updateIdle()
if (msg instanceof LDAPResult) {
if (expect.indexOf(msg.status) === -1) {
if (msg.status !== 0 && expect.indexOf(msg.status) === -1) {
return sendResult('error', errors.getError(msg))
}
return sendResult('end', msg)
@ -1230,7 +1277,7 @@ Client.prototype._sendSocket = function _sendSocket (message,
function onRequestTimeout () {
self.emit('timeout', message)
const cb = tracker.fetch(message.messageID)
const { callback: cb } = tracker.fetch(message.messageId)
if (cb) {
// FIXME: the timed-out request should be abandoned
cb(new errors.TimeoutError('request timeout (client interrupt)'))
@ -1239,8 +1286,8 @@ Client.prototype._sendSocket = function _sendSocket (message,
function writeCallback () {
if (expect === 'abandon') {
// Mark the messageID specified as abandoned
tracker.abandon(message.abandonID)
// Mark the messageId specified as abandoned
tracker.abandon(message.abandonId)
// No need to track the abandon request itself
tracker.remove(message.id)
return callback(null)
@ -1272,10 +1319,11 @@ Client.prototype._sendSocket = function _sendSocket (message,
timer = setTimeout(onRequestTimeout, self.timeout)
}
log.trace('sending request %j', message.json)
log.trace('sending request %j', message.pojo)
try {
return conn.write(message.toBer(), writeCallback)
const messageBer = message.toBer()
return conn.write(messageBer.buffer, writeCallback)
} catch (e) {
if (timer) { clearTimeout(timer) }

View File

@ -4,7 +4,7 @@ const logger = require('../logger')
const Client = require('./client')
module.exports = {
Client: Client,
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')

View File

@ -62,13 +62,23 @@ module.exports = function messageTrackerFactory (options) {
*/
tracker.abandon = function abandonMessage (msgID) {
if (messages.has(msgID) === false) return false
const toAbandon = messages.get(msgID)
abandoned.set(msgID, {
age: currentID,
cb: messages.get(msgID)
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.
@ -79,10 +89,10 @@ module.exports = function messageTrackerFactory (options) {
* @method fetch
*/
tracker.fetch = function fetchMessage (msgID) {
const messageCB = messages.get(msgID)
if (messageCB) {
const tracked = messages.get(msgID)
if (tracked) {
purgeAbandoned(msgID, abandoned)
return messageCB
return tracked
}
// We sent an abandon request but the server either wasn't able to process
@ -91,7 +101,7 @@ module.exports = function messageTrackerFactory (options) {
// to be processed normally.
const abandonedMsg = abandoned.get(msgID)
if (abandonedMsg) {
return abandonedMsg.cb
return { message: abandonedMsg, callback: abandonedMsg.cb }
}
return null
@ -110,7 +120,7 @@ module.exports = function messageTrackerFactory (options) {
messages.forEach((val, key) => {
purgeAbandoned(key, abandoned)
tracker.remove(key)
cb(key, val)
cb(key, val.callback)
})
}
@ -132,7 +142,7 @@ module.exports = function messageTrackerFactory (options) {
* 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`.
* have a new property added to it: `messageId`.
* @param {function} callback The handler for the message.
*
* @memberof MessageTracker
@ -143,8 +153,8 @@ module.exports = function messageTrackerFactory (options) {
// 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.messageId = currentID
messages.set(currentID, { callback, message })
}
return tracker

View File

@ -14,7 +14,7 @@
* is not accepting any requests.
*/
module.exports = function enqueue (message, expect, emitter, cb) {
if (this._queue.length >= this.size || this._frozen) {
if (this._queue.size >= this.size || this._frozen) {
return false
}

View File

@ -2,14 +2,8 @@
const EventEmitter = require('events').EventEmitter
const util = require('util')
const assert = require('assert-plus')
// var dn = require('../dn')
// var messages = require('../messages/index')
// var Protocol = require('../protocol')
const PagedControl = require('../controls/paged_results_control.js')
const { PagedResultsControl } = require('@ldapjs/controls')
const CorkedEmitter = require('../corked_emitter.js')
/// --- API
@ -47,7 +41,7 @@ function SearchPager (opts) {
this.sendRequest = opts.sendRequest
this.controls.forEach(function (control) {
if (control.type === PagedControl.OID) {
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')
@ -79,7 +73,7 @@ SearchPager.prototype._onEnd = function _onEnd (res) {
const self = this
let cookie = null
res.controls.forEach(function (control) {
if (control.type === PagedControl.OID) {
if (control.type === PagedResultsControl.OID) {
cookie = control.value.cookie
}
})
@ -95,13 +89,13 @@ SearchPager.prototype._onEnd = function _onEnd (res) {
if (this.listeners('pageError').length > 0) {
this.emit('pageError', err)
// If the consumer as subscribed to pageError, SearchPager is absolved
// from deliverying the fault via the 'error' event. Emitting an 'end'
// 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 explaination above.
// No end event possible per explanation above.
}
return
}
@ -140,10 +134,10 @@ SearchPager.prototype._onError = function _onError (err) {
*/
SearchPager.prototype._nextPage = function _nextPage (cookie) {
const controls = this.controls.slice(0)
controls.push(new PagedControl({
controls.push(new PagedResultsControl({
value: {
size: this.pageSize,
cookie: cookie
cookie
}
}))

View File

@ -1,61 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
// var asn1 = require('asn1')
// var Protocol = require('../protocol')
/// --- Globals
// var Ber = asn1.Ber
/// --- API
function Control (options) {
assert.optionalObject(options)
options = options || {}
assert.optionalString(options.type)
assert.optionalBool(options.criticality)
if (options.value) {
assert.buffer(options.value)
}
this.type = options.type || ''
this.criticality = options.critical || options.criticality || false
this.value = options.value || null
}
Object.defineProperties(Control.prototype, {
json: {
get: function getJson () {
const obj = {
controlType: this.type,
criticality: this.criticality,
controlValue: this.value
}
return (typeof (this._json) === 'function' ? this._json(obj) : obj)
}
}
})
Control.prototype.toBer = function toBer (ber) {
assert.ok(ber)
ber.startSequence()
ber.writeString(this.type || '')
ber.writeBoolean(this.criticality)
if (typeof (this._toBer) === 'function') {
this._toBer(ber)
} else {
if (this.value) { ber.writeString(this.value) }
}
ber.endSequence()
}
Control.prototype.toString = function toString () {
return this.json
}
/// --- Exports
module.exports = Control

View File

@ -1,83 +0,0 @@
const assert = require('assert-plus')
const util = require('util')
const asn1 = require('asn1')
const Control = require('./control')
/// --- Globals
const BerReader = asn1.BerReader
const BerWriter = asn1.BerWriter
/// --- API
function EntryChangeNotificationControl (options) {
assert.optionalObject(options)
options = options || {}
options.type = EntryChangeNotificationControl.OID
if (options.value) {
if (Buffer.isBuffer(options.value)) {
this.parse(options.value)
} else if (typeof (options.value) === 'object') {
this._value = options.value
} else {
throw new TypeError('options.value must be a Buffer or Object')
}
options.value = null
}
Control.call(this, options)
}
util.inherits(EntryChangeNotificationControl, Control)
Object.defineProperties(EntryChangeNotificationControl.prototype, {
value: {
get: function () { return this._value || {} },
configurable: false
}
})
EntryChangeNotificationControl.prototype.parse = function parse (buffer) {
assert.ok(buffer)
const ber = new BerReader(buffer)
if (ber.readSequence()) {
this._value = {
changeType: ber.readInt()
}
// if the operation was moddn, then parse the optional previousDN attr
if (this._value.changeType === 8) { this._value.previousDN = ber.readString() }
this._value.changeNumber = ber.readInt()
return true
}
return false
}
EntryChangeNotificationControl.prototype._toBer = function (ber) {
assert.ok(ber)
if (!this._value) { return }
const writer = new BerWriter()
writer.startSequence()
writer.writeInt(this.value.changeType)
if (this.value.previousDN) { writer.writeString(this.value.previousDN) }
writer.writeInt(parseInt(this.value.changeNumber, 10))
writer.endSequence()
ber.writeBuffer(writer.buffer, 0x04)
}
EntryChangeNotificationControl.prototype._json = function (obj) {
obj.controlValue = this.value
return obj
}
EntryChangeNotificationControl.OID = '2.16.840.1.113730.3.4.7'
/// --- Exports
module.exports = EntryChangeNotificationControl

View File

@ -1,86 +1,4 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert')
const Ber = require('asn1').Ber
const Control = require('./control')
const EntryChangeNotificationControl =
require('./entry_change_notification_control')
const PersistentSearchControl = require('./persistent_search_control')
const PagedResultsControl = require('./paged_results_control')
const ServerSideSortingRequestControl =
require('./server_side_sorting_request_control.js')
const ServerSideSortingResponseControl =
require('./server_side_sorting_response_control.js')
const VirtualListViewRequestControl =
require('./virtual_list_view_request_control.js')
const VirtualListViewResponseControl =
require('./virtual_list_view_response_control.js')
/// --- API
module.exports = {
getControl: function getControl (ber) {
assert.ok(ber)
if (ber.readSequence() === null) { return null }
let type
const opts = {
criticality: false,
value: null
}
if (ber.length) {
const end = ber.offset + ber.length
type = ber.readString()
if (ber.offset < end) {
if (ber.peek() === Ber.Boolean) { opts.criticality = ber.readBoolean() }
}
if (ber.offset < end) { opts.value = ber.readString(Ber.OctetString, true) }
}
let control
switch (type) {
case PersistentSearchControl.OID:
control = new PersistentSearchControl(opts)
break
case EntryChangeNotificationControl.OID:
control = new EntryChangeNotificationControl(opts)
break
case PagedResultsControl.OID:
control = new PagedResultsControl(opts)
break
case ServerSideSortingRequestControl.OID:
control = new ServerSideSortingRequestControl(opts)
break
case ServerSideSortingResponseControl.OID:
control = new ServerSideSortingResponseControl(opts)
break
case VirtualListViewRequestControl.OID:
control = new VirtualListViewRequestControl(opts)
break
case VirtualListViewResponseControl.OID:
control = new VirtualListViewResponseControl(opts)
break
default:
opts.type = type
control = new Control(opts)
break
}
return control
},
Control: Control,
EntryChangeNotificationControl: EntryChangeNotificationControl,
PagedResultsControl: PagedResultsControl,
PersistentSearchControl: PersistentSearchControl,
ServerSideSortingRequestControl: ServerSideSortingRequestControl,
ServerSideSortingResponseControl: ServerSideSortingResponseControl,
VirtualListViewRequestControl: VirtualListViewRequestControl,
VirtualListViewResponseControl: VirtualListViewResponseControl
}
const controls = require('@ldapjs/controls')
module.exports = controls

View File

@ -1,82 +0,0 @@
const assert = require('assert-plus')
const util = require('util')
const asn1 = require('asn1')
const Control = require('./control')
/// --- Globals
const BerReader = asn1.BerReader
const BerWriter = asn1.BerWriter
/// --- API
function PagedResultsControl (options) {
assert.optionalObject(options)
options = options || {}
options.type = PagedResultsControl.OID
if (options.value) {
if (Buffer.isBuffer(options.value)) {
this.parse(options.value)
} else if (typeof (options.value) === 'object') {
this._value = options.value
} else {
throw new TypeError('options.value must be a Buffer or Object')
}
options.value = null
}
Control.call(this, options)
}
util.inherits(PagedResultsControl, Control)
Object.defineProperties(PagedResultsControl.prototype, {
value: {
get: function () { return this._value || {} },
configurable: false
}
})
PagedResultsControl.prototype.parse = function parse (buffer) {
assert.ok(buffer)
const ber = new BerReader(buffer)
if (ber.readSequence()) {
this._value = {}
this._value.size = ber.readInt()
this._value.cookie = ber.readString(asn1.Ber.OctetString, true)
// readString returns '' instead of a zero-length buffer
if (!this._value.cookie) { this._value.cookie = Buffer.alloc(0) }
return true
}
return false
}
PagedResultsControl.prototype._toBer = function (ber) {
assert.ok(ber)
if (!this._value) { return }
const writer = new BerWriter()
writer.startSequence()
writer.writeInt(this.value.size)
if (this.value.cookie && this.value.cookie.length > 0) {
writer.writeBuffer(this.value.cookie, asn1.Ber.OctetString)
} else {
writer.writeString('') // writeBuffer rejects zero-length buffers
}
writer.endSequence()
ber.writeBuffer(writer.buffer, 0x04)
}
PagedResultsControl.prototype._json = function (obj) {
obj.controlValue = this.value
return obj
}
PagedResultsControl.OID = '1.2.840.113556.1.4.319'
/// --- Exports
module.exports = PagedResultsControl

View File

@ -1,82 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const asn1 = require('asn1')
const Control = require('./control')
/// --- Globals
const BerReader = asn1.BerReader
const BerWriter = asn1.BerWriter
/// --- API
function PersistentSearchControl (options) {
assert.optionalObject(options)
options = options || {}
options.type = PersistentSearchControl.OID
if (options.value) {
if (Buffer.isBuffer(options.value)) {
this.parse(options.value)
} else if (typeof (options.value) === 'object') {
this._value = options.value
} else {
throw new TypeError('options.value must be a Buffer or Object')
}
options.value = null
}
Control.call(this, options)
}
util.inherits(PersistentSearchControl, Control)
Object.defineProperties(PersistentSearchControl.prototype, {
value: {
get: function () { return this._value || {} },
configurable: false
}
})
PersistentSearchControl.prototype.parse = function parse (buffer) {
assert.ok(buffer)
const ber = new BerReader(buffer)
if (ber.readSequence()) {
this._value = {
changeTypes: ber.readInt(),
changesOnly: ber.readBoolean(),
returnECs: ber.readBoolean()
}
return true
}
return false
}
PersistentSearchControl.prototype._toBer = function (ber) {
assert.ok(ber)
if (!this._value) { return }
const writer = new BerWriter()
writer.startSequence()
writer.writeInt(this.value.changeTypes)
writer.writeBoolean(this.value.changesOnly)
writer.writeBoolean(this.value.returnECs)
writer.endSequence()
ber.writeBuffer(writer.buffer, 0x04)
}
PersistentSearchControl.prototype._json = function (obj) {
obj.controlValue = this.value
return obj
}
PersistentSearchControl.OID = '2.16.840.1.113730.3.4.3'
/// --- Exports
module.exports = PersistentSearchControl

View File

@ -1,108 +0,0 @@
const assert = require('assert-plus')
const util = require('util')
const asn1 = require('asn1')
const Control = require('./control')
/// --- Globals
const BerReader = asn1.BerReader
const BerWriter = asn1.BerWriter
/// --- API
function ServerSideSortingRequestControl (options) {
assert.optionalObject(options)
options = options || {}
options.type = ServerSideSortingRequestControl.OID
if (options.value) {
if (Buffer.isBuffer(options.value)) {
this.parse(options.value)
} else if (Array.isArray(options.value)) {
assert.arrayOfObject(options.value, 'options.value must be Objects')
for (let i = 0; i < options.value.length; i++) {
if (Object.prototype.hasOwnProperty.call(options.value[i], 'attributeType') === false) {
throw new Error('Missing required key: attributeType')
}
}
this._value = options.value
} else if (typeof (options.value) === 'object') {
if (Object.prototype.hasOwnProperty.call(options.value, 'attributeType') === false) {
throw new Error('Missing required key: attributeType')
}
this._value = [options.value]
} else {
throw new TypeError('options.value must be a Buffer, Array or Object')
}
options.value = null
}
Control.call(this, options)
}
util.inherits(ServerSideSortingRequestControl, Control)
Object.defineProperties(ServerSideSortingRequestControl.prototype, {
value: {
get: function () { return this._value || [] },
configurable: false
}
})
ServerSideSortingRequestControl.prototype.parse = function parse (buffer) {
assert.ok(buffer)
const ber = new BerReader(buffer)
let item
if (ber.readSequence(0x30)) {
this._value = []
while (ber.readSequence(0x30)) {
item = {}
item.attributeType = ber.readString(asn1.Ber.OctetString)
if (ber.peek() === 0x80) {
item.orderingRule = ber.readString(0x80)
}
if (ber.peek() === 0x81) {
item.reverseOrder = (ber._readTag(0x81) !== 0)
}
this._value.push(item)
}
return true
}
return false
}
ServerSideSortingRequestControl.prototype._toBer = function (ber) {
assert.ok(ber)
if (!this._value || this.value.length === 0) { return }
const writer = new BerWriter()
writer.startSequence(0x30)
for (let i = 0; i < this.value.length; i++) {
const item = this.value[i]
writer.startSequence(0x30)
if (item.attributeType) {
writer.writeString(item.attributeType, asn1.Ber.OctetString)
}
if (item.orderingRule) {
writer.writeString(item.orderingRule, 0x80)
}
if (item.reverseOrder) {
writer.writeBoolean(item.reverseOrder, 0x81)
}
writer.endSequence()
}
writer.endSequence()
ber.writeBuffer(writer.buffer, 0x04)
}
ServerSideSortingRequestControl.prototype._json = function (obj) {
obj.controlValue = this.value
return obj
}
ServerSideSortingRequestControl.OID = '1.2.840.113556.1.4.473'
/// ---Exports
module.exports = ServerSideSortingRequestControl

View File

@ -1,100 +0,0 @@
const assert = require('assert-plus')
const util = require('util')
const asn1 = require('asn1')
const Control = require('./control')
const CODES = require('../errors/codes')
/// --- Globals
const BerReader = asn1.BerReader
const BerWriter = asn1.BerWriter
const VALID_CODES = [
CODES.LDAP_SUCCESS,
CODES.LDAP_OPERATIONS_ERROR,
CODES.LDAP_TIME_LIMIT_EXCEEDED,
CODES.LDAP_STRONG_AUTH_REQUIRED,
CODES.LDAP_ADMIN_LIMIT_EXCEEDED,
CODES.LDAP_NO_SUCH_ATTRIBUTE,
CODES.LDAP_INAPPROPRIATE_MATCHING,
CODES.LDAP_INSUFFICIENT_ACCESS_RIGHTS,
CODES.LDAP_BUSY,
CODES.LDAP_UNWILLING_TO_PERFORM,
CODES.LDAP_OTHER
]
function ServerSideSortingResponseControl (options) {
assert.optionalObject(options)
options = options || {}
options.type = ServerSideSortingResponseControl.OID
options.criticality = false
if (options.value) {
if (Buffer.isBuffer(options.value)) {
this.parse(options.value)
} else if (typeof (options.value) === 'object') {
if (VALID_CODES.indexOf(options.value.result) === -1) {
throw new Error('Invalid result code')
}
if (options.value.failedAttribute &&
typeof (options.value.failedAttribute) !== 'string') {
throw new Error('failedAttribute must be String')
}
this._value = options.value
} else {
throw new TypeError('options.value must be a Buffer or Object')
}
options.value = null
}
Control.call(this, options)
}
util.inherits(ServerSideSortingResponseControl, Control)
Object.defineProperties(ServerSideSortingResponseControl.prototype, {
value: {
get: function () { return this._value || {} },
configurable: false
}
})
ServerSideSortingResponseControl.prototype.parse = function parse (buffer) {
assert.ok(buffer)
const ber = new BerReader(buffer)
if (ber.readSequence(0x30)) {
this._value = {}
this._value.result = ber.readEnumeration()
if (ber.peek() === 0x80) {
this._value.failedAttribute = ber.readString(0x80)
}
return true
}
return false
}
ServerSideSortingResponseControl.prototype._toBer = function (ber) {
assert.ok(ber)
if (!this._value || this.value.length === 0) { return }
const writer = new BerWriter()
writer.startSequence(0x30)
writer.writeEnumeration(this.value.result)
if (this.value.result !== CODES.LDAP_SUCCESS && this.value.failedAttribute) {
writer.writeString(this.value.failedAttribute, 0x80)
}
writer.endSequence()
ber.writeBuffer(writer.buffer, 0x04)
}
ServerSideSortingResponseControl.prototype._json = function (obj) {
obj.controlValue = this.value
return obj
}
ServerSideSortingResponseControl.OID = '1.2.840.113556.1.4.474'
/// --- Exports
module.exports = ServerSideSortingResponseControl

View File

@ -1,94 +0,0 @@
const assert = require('assert-plus')
const util = require('util')
const asn1 = require('asn1')
const Control = require('./control')
/// --- Globals
const BerReader = asn1.BerReader
const BerWriter = asn1.BerWriter
/// --- API
function VirtualListViewControl (options) {
assert.optionalObject(options)
options = options || {}
options.type = VirtualListViewControl.OID
if (options.value) {
if (Buffer.isBuffer(options.value)) {
this.parse(options.value)
} else if (typeof (options.value) === 'object') {
if (Object.prototype.hasOwnProperty.call(options.value, 'beforeCount') === false) {
throw new Error('Missing required key: beforeCount')
}
if (Object.prototype.hasOwnProperty.call(options.value, 'afterCount') === false) {
throw new Error('Missing required key: afterCount')
}
this._value = options.value
} else {
throw new TypeError('options.value must be a Buffer or Object')
}
options.value = null
}
Control.call(this, options)
}
util.inherits(VirtualListViewControl, Control)
Object.defineProperties(VirtualListViewControl.prototype, {
value: {
get: function () { return this._value || [] },
configurable: false
}
})
VirtualListViewControl.prototype.parse = function parse (buffer) {
assert.ok(buffer)
const ber = new BerReader(buffer)
if (ber.readSequence()) {
this._value = {}
this._value.beforeCount = ber.readInt()
this._value.afterCount = ber.readInt()
if (ber.peek() === 0xa0) {
if (ber.readSequence(0xa0)) {
this._value.targetOffset = ber.readInt()
this._value.contentCount = ber.readInt()
}
}
if (ber.peek() === 0x81) {
this._value.greaterThanOrEqual = ber.readString(0x81)
}
return true
}
return false
}
VirtualListViewControl.prototype._toBer = function (ber) {
assert.ok(ber)
if (!this._value || this.value.length === 0) {
return
}
const writer = new BerWriter()
writer.startSequence(0x30)
writer.writeInt(this.value.beforeCount)
writer.writeInt(this.value.afterCount)
if (this.value.targetOffset !== undefined) {
writer.startSequence(0xa0)
writer.writeInt(this.value.targetOffset)
writer.writeInt(this.value.contentCount)
writer.endSequence()
} else if (this.value.greaterThanOrEqual !== undefined) {
writer.writeString(this.value.greaterThanOrEqual, 0x81)
}
writer.endSequence()
ber.writeBuffer(writer.buffer, 0x04)
}
VirtualListViewControl.prototype._json = function (obj) {
obj.controlValue = this.value
return obj
}
VirtualListViewControl.OID = '2.16.840.1.113730.3.4.9'
/// ---Exports
module.exports = VirtualListViewControl

View File

@ -1,112 +0,0 @@
const assert = require('assert-plus')
const util = require('util')
const asn1 = require('asn1')
const Control = require('./control')
const CODES = require('../errors/codes')
/// --- Globals
const BerReader = asn1.BerReader
const BerWriter = asn1.BerWriter
const VALID_CODES = [
CODES.LDAP_SUCCESS,
CODES.LDAP_OPERATIONS_ERROR,
CODES.LDAP_UNWILLING_TO_PERFORM,
CODES.LDAP_INSUFFICIENT_ACCESS_RIGHTS,
CODES.LDAP_BUSY,
CODES.LDAP_TIME_LIMIT_EXCEEDED,
CODES.LDAP_ADMIN_LIMIT_EXCEEDED,
CODES.LDAP_SORT_CONTROL_MISSING,
CODES.LDAP_INDEX_RANGE_ERROR,
CODES.LDAP_CONTROL_ERROR,
CODES.LDAP_OTHER
]
function VirtualListViewResponseControl (options) {
assert.optionalObject(options)
options = options || {}
options.type = VirtualListViewResponseControl.OID
options.criticality = false
if (options.value) {
if (Buffer.isBuffer(options.value)) {
this.parse(options.value)
} else if (typeof (options.value) === 'object') {
if (VALID_CODES.indexOf(options.value.result) === -1) {
throw new Error('Invalid result code')
}
this._value = options.value
} else {
throw new TypeError('options.value must be a Buffer or Object')
}
options.value = null
}
Control.call(this, options)
}
util.inherits(VirtualListViewResponseControl, Control)
Object.defineProperties(VirtualListViewResponseControl.prototype, {
value: {
get: function () { return this._value || {} },
configurable: false
}
})
VirtualListViewResponseControl.prototype.parse = function parse (buffer) {
assert.ok(buffer)
const ber = new BerReader(buffer)
if (ber.readSequence()) {
this._value = {}
if (ber.peek(0x02)) {
this._value.targetPosition = ber.readInt()
}
if (ber.peek(0x02)) {
this._value.contentCount = ber.readInt()
}
this._value.result = ber.readEnumeration()
this._value.cookie = ber.readString(asn1.Ber.OctetString, true)
// readString returns '' instead of a zero-length buffer
if (!this._value.cookie) {
this._value.cookie = Buffer.alloc(0)
}
return true
}
return false
}
VirtualListViewResponseControl.prototype._toBer = function (ber) {
assert.ok(ber)
if (!this._value || this.value.length === 0) {
return
}
const writer = new BerWriter()
writer.startSequence()
if (this.value.targetPosition !== undefined) {
writer.writeInt(this.value.targetPosition)
}
if (this.value.contentCount !== undefined) {
writer.writeInt(this.value.contentCount)
}
writer.writeEnumeration(this.value.result)
if (this.value.cookie && this.value.cookie.length > 0) {
writer.writeBuffer(this.value.cookie, asn1.Ber.OctetString)
} else {
writer.writeString('') // writeBuffer rejects zero-length buffers
}
writer.endSequence()
ber.writeBuffer(writer.buffer, 0x04)
}
VirtualListViewResponseControl.prototype._json = function (obj) {
obj.controlValue = this.value
return obj
}
VirtualListViewResponseControl.OID = '2.16.840.1.113730.3.4.10'
/// --- Exports
module.exports = VirtualListViewResponseControl

473
lib/dn.js
View File

@ -1,473 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
/// --- Helpers
function invalidDN (name) {
const e = new Error()
e.name = 'InvalidDistinguishedNameError'
e.message = name
return e
}
function isAlphaNumeric (c) {
const re = /[A-Za-z0-9]/
return re.test(c)
}
function isWhitespace (c) {
const re = /\s/
return re.test(c)
}
function repeatChar (c, n) {
let out = ''
const max = n || 0
for (let i = 0; i < max; i++) { out += c }
return out
}
/// --- API
function RDN (obj) {
const self = this
this.attrs = {}
if (obj) {
Object.keys(obj).forEach(function (k) {
self.set(k, obj[k])
})
}
}
RDN.prototype.set = function rdnSet (name, value, opts) {
assert.string(name, 'name (string) required')
assert.string(value, 'value (string) required')
const self = this
const lname = name.toLowerCase()
this.attrs[lname] = {
value: value,
name: name
}
if (opts && typeof (opts) === 'object') {
Object.keys(opts).forEach(function (k) {
if (k !== 'value') { self.attrs[lname][k] = opts[k] }
})
}
}
RDN.prototype.equals = function rdnEquals (rdn) {
if (typeof (rdn) !== 'object') { return false }
const ourKeys = Object.keys(this.attrs)
const theirKeys = Object.keys(rdn.attrs)
if (ourKeys.length !== theirKeys.length) { return false }
ourKeys.sort()
theirKeys.sort()
for (let i = 0; i < ourKeys.length; i++) {
if (ourKeys[i] !== theirKeys[i]) { return false }
if (this.attrs[ourKeys[i]].value !== rdn.attrs[ourKeys[i]].value) { return false }
}
return true
}
/**
* Convert RDN to string according to specified formatting options.
* (see: DN.format for option details)
*/
RDN.prototype.format = function rdnFormat (options) {
assert.optionalObject(options, 'options must be an object')
options = options || {}
const self = this
let str = ''
function escapeValue (val, forceQuote) {
let out = ''
let cur = 0
const len = val.length
let quoted = false
/* BEGIN JSSTYLED */
// TODO: figure out what this regex is actually trying to test for and
// fix it to appease the linter.
/* eslint-disable-next-line no-useless-escape */
const escaped = /[\\\"]/
const special = /[,=+<>#;]/
/* END JSSTYLED */
if (len > 0) {
// Wrap strings with trailing or leading spaces in quotes
quoted = forceQuote || (val[0] === ' ' || val[len - 1] === ' ')
}
while (cur < len) {
if (escaped.test(val[cur]) || (!quoted && special.test(val[cur]))) {
out += '\\'
}
out += val[cur++]
}
if (quoted) { out = '"' + out + '"' }
return out
}
function sortParsed (a, b) {
return self.attrs[a].order - self.attrs[b].order
}
function sortStandard (a, b) {
const nameCompare = a.localeCompare(b)
if (nameCompare === 0) {
// TODO: Handle binary values
return self.attrs[a].value.localeCompare(self.attrs[b].value)
} else {
return nameCompare
}
}
const keys = Object.keys(this.attrs)
if (options.keepOrder) {
keys.sort(sortParsed)
} else {
keys.sort(sortStandard)
}
keys.forEach(function (key) {
const attr = self.attrs[key]
if (str.length) { str += '+' }
if (options.keepCase) {
str += attr.name
} else {
if (options.upperName) { str += key.toUpperCase() } else { str += key }
}
str += '=' + escapeValue(attr.value, (options.keepQuote && attr.quoted))
})
return str
}
RDN.prototype.toString = function rdnToString () {
return this.format()
}
// Thank you OpenJDK!
function parse (name) {
if (typeof (name) !== 'string') { throw new TypeError('name (string) required') }
let cur = 0
const len = name.length
function parseRdn () {
const rdn = new RDN()
let order = 0
rdn.spLead = trim()
while (cur < len) {
const opts = {
order: order
}
const attr = parseAttrType()
trim()
if (cur >= len || name[cur++] !== '=') { throw invalidDN(name) }
trim()
// Parameters about RDN value are set in 'opts' by parseAttrValue
const value = parseAttrValue(opts)
rdn.set(attr, value, opts)
rdn.spTrail = trim()
if (cur >= len || name[cur] !== '+') { break }
++cur
++order
}
return rdn
}
function trim () {
let count = 0
while ((cur < len) && isWhitespace(name[cur])) {
++cur
count++
}
return count
}
function parseAttrType () {
const beg = cur
while (cur < len) {
const c = name[cur]
if (isAlphaNumeric(c) ||
c === '.' ||
c === '-' ||
c === ' ') {
++cur
} else {
break
}
}
// Back out any trailing spaces.
while ((cur > beg) && (name[cur - 1] === ' ')) { --cur }
if (beg === cur) { throw invalidDN(name) }
return name.slice(beg, cur)
}
function parseAttrValue (opts) {
if (cur < len && name[cur] === '#') {
opts.binary = true
return parseBinaryAttrValue()
} else if (cur < len && name[cur] === '"') {
opts.quoted = true
return parseQuotedAttrValue()
} else {
return parseStringAttrValue()
}
}
function parseBinaryAttrValue () {
const beg = cur++
while (cur < len && isAlphaNumeric(name[cur])) { ++cur }
return name.slice(beg, cur)
}
function parseQuotedAttrValue () {
let str = ''
++cur // Consume the first quote
while ((cur < len) && name[cur] !== '"') {
if (name[cur] === '\\') { cur++ }
str += name[cur++]
}
if (cur++ >= len) {
// no closing quote
throw invalidDN(name)
}
return str
}
function parseStringAttrValue () {
const beg = cur
let str = ''
let esc = -1
while ((cur < len) && !atTerminator()) {
if (name[cur] === '\\') {
// Consume the backslash and mark its place just in case it's escaping
// whitespace which needs to be preserved.
esc = cur++
}
if (cur === len) {
// backslash followed by nothing
throw invalidDN(name)
}
str += name[cur++]
}
// Trim off (unescaped) trailing whitespace and rewind cursor to the end of
// the AttrValue to record whitespace length.
for (; cur > beg; cur--) {
if (!isWhitespace(name[cur - 1]) || (esc === (cur - 1))) { break }
}
return str.slice(0, cur - beg)
}
function atTerminator () {
return (cur < len &&
(name[cur] === ',' ||
name[cur] === ';' ||
name[cur] === '+'))
}
const rdns = []
// Short-circuit for empty DNs
if (len === 0) { return new DN(rdns) }
rdns.push(parseRdn())
while (cur < len) {
if (name[cur] === ',' || name[cur] === ';') {
++cur
rdns.push(parseRdn())
} else {
throw invalidDN(name)
}
}
return new DN(rdns)
}
function DN (rdns) {
assert.optionalArrayOfObject(rdns, '[object] required')
this.rdns = rdns ? rdns.slice() : []
this._format = {}
}
Object.defineProperties(DN.prototype, {
length: {
get: function getLength () { return this.rdns.length },
configurable: false
}
})
/**
* Convert DN to string according to specified formatting options.
*
* Parameters:
* - options: formatting parameters (optional, details below)
*
* Options are divided into two types:
* - Preservation options: Using data recorded during parsing, details of the
* original DN are preserved when converting back into a string.
* - 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 attr 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
*/
DN.prototype.format = function dnFormat (options) {
assert.optionalObject(options, 'options must be an object')
options = options || this._format
let str = ''
this.rdns.forEach(function (rdn) {
const rdnString = rdn.format(options)
if (str.length !== 0) {
str += ','
}
if (options.keepSpace) {
str += (repeatChar(' ', rdn.spLead) +
rdnString + repeatChar(' ', rdn.spTrail))
} else if (options.skipSpace === true || str.length === 0) {
str += rdnString
} else {
str += ' ' + rdnString
}
})
return str
}
/**
* Set default string formatting options.
*/
DN.prototype.setFormat = function setFormat (options) {
assert.object(options, 'options must be an object')
this._format = options
}
DN.prototype.toString = function dnToString () {
return this.format()
}
DN.prototype.parentOf = function parentOf (dn) {
if (typeof (dn) !== 'object') { dn = parse(dn) }
if (this.rdns.length >= dn.rdns.length) { return false }
const diff = dn.rdns.length - this.rdns.length
for (let i = this.rdns.length - 1; i >= 0; i--) {
const myRDN = this.rdns[i]
const theirRDN = dn.rdns[i + diff]
if (!myRDN.equals(theirRDN)) { return false }
}
return true
}
DN.prototype.childOf = function childOf (dn) {
if (typeof (dn) !== 'object') { dn = parse(dn) }
return dn.parentOf(this)
}
DN.prototype.isEmpty = function isEmpty () {
return (this.rdns.length === 0)
}
DN.prototype.equals = function dnEquals (dn) {
if (typeof (dn) !== 'object') { dn = parse(dn) }
if (this.rdns.length !== dn.rdns.length) { return false }
for (let i = 0; i < this.rdns.length; i++) {
if (!this.rdns[i].equals(dn.rdns[i])) { return false }
}
return true
}
DN.prototype.parent = function dnParent () {
if (this.rdns.length !== 0) {
const save = this.rdns.shift()
const dn = new DN(this.rdns)
this.rdns.unshift(save)
return dn
}
return null
}
DN.prototype.clone = function dnClone () {
const dn = new DN(this.rdns)
dn._format = this._format
return dn
}
DN.prototype.reverse = function dnReverse () {
this.rdns.reverse()
return this
}
DN.prototype.pop = function dnPop () {
return this.rdns.pop()
}
DN.prototype.push = function dnPush (rdn) {
assert.object(rdn, 'rdn (RDN) required')
return this.rdns.push(rdn)
}
DN.prototype.shift = function dnShift () {
return this.rdns.shift()
}
DN.prototype.unshift = function dnUnshift (rdn) {
assert.object(rdn, 'rdn (RDN) required')
return this.rdns.unshift(rdn)
}
DN.isDN = function isDN (dn) {
if (!dn || typeof (dn) !== 'object') {
return false
}
if (dn instanceof DN) {
return true
}
if (Array.isArray(dn.rdns)) {
// Really simple duck-typing for now
return true
}
return false
}
/// --- Exports
module.exports = {
parse: parse,
DN: DN,
RDN: RDN
}

View File

@ -1,120 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.s
/// --- Globals
let SERVER_PROVIDER
let DTRACE_ID = 0
const MAX_INT = 4294967295
/*
* Args:
* server-*-start:
* 0 -> id
* 1 -> remoteIP
* 2 -> bindDN
* 3 -> req.dn
* 4,5 -> op specific
*
* server-*-done:
* 0 -> id
* 1 -> remoteIp
* 2 -> bindDN
* 3 -> requsetDN
* 4 -> status
* 5 -> errorMessage
*
*/
const SERVER_PROBES = {
// 4: attributes.length
'server-add-start': ['int', 'char *', 'char *', 'char *', 'int'],
'server-add-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
'server-bind-start': ['int', 'char *', 'char *', 'char *'],
'server-bind-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
// 4: attribute, 5: value
'server-compare-start': ['int', 'char *', 'char *', 'char *',
'char *', 'char *'],
'server-compare-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
'server-delete-start': ['int', 'char *', 'char *', 'char *'],
'server-delete-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
// 4: requestName, 5: requestValue
'server-exop-start': ['int', 'char *', 'char *', 'char *', 'char *',
'char *'],
'server-exop-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
// 4: changes.length
'server-modify-start': ['int', 'char *', 'char *', 'char *', 'int'],
'server-modify-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
// 4: newRdn, 5: newSuperior
'server-modifydn-start': ['int', 'char *', 'char *', 'char *', 'char *',
'char *'],
'server-modifydn-done': ['int', 'char *', 'char *', 'char *', 'int',
'char *'],
// 4: scope, 5: filter
'server-search-start': ['int', 'char *', 'char *', 'char *', 'char *',
'char *'],
'server-search-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
// Last two are searchEntry.DN and seachEntry.attributes.length
'server-search-entry': ['int', 'char *', 'char *', 'char *', 'char *', 'int'],
'server-unbind-start': ['int', 'char *', 'char *', 'char *'],
'server-unbind-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
'server-abandon-start': ['int', 'char *', 'char *', 'char *'],
'server-abandon-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
// remote IP
'server-connection': ['char *']
}
/// --- API
module.exports = (function () {
if (!SERVER_PROVIDER) {
try {
const dtrace = require('dtrace-provider')
SERVER_PROVIDER = dtrace.createDTraceProvider('ldapjs')
Object.keys(SERVER_PROBES).forEach(function (p) {
const args = SERVER_PROBES[p].splice(0)
args.unshift(p)
dtrace.DTraceProvider.prototype.addProbe.apply(SERVER_PROVIDER, args)
})
} catch (e) {
SERVER_PROVIDER = {
fire: function () {
},
enable: function () {
},
addProbe: function () {
const p = {
fire: function () {
}
}
return (p)
},
removeProbe: function () {
},
disable: function () {
}
}
}
SERVER_PROVIDER.enable()
SERVER_PROVIDER._nextId = function () {
if (DTRACE_ID === MAX_INT) { DTRACE_ID = 0 }
return ++DTRACE_ID
}
}
return SERVER_PROVIDER
}())

View File

@ -86,7 +86,7 @@ Object.keys(CODES).forEach(function (code) {
})
ERRORS[CODES[code]] = {
err: err,
err,
message: msg
}
})

View File

@ -1,27 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert')
const util = require('util')
const parents = require('ldap-filter')
const Filter = require('./filter')
/// --- API
function AndFilter (options) {
parents.AndFilter.call(this, options)
}
util.inherits(AndFilter, parents.AndFilter)
Filter.mixin(AndFilter)
module.exports = AndFilter
AndFilter.prototype._toBer = function (ber) {
assert.ok(ber)
this.filters.forEach(function (f) {
ber = f.toBer(ber)
})
return ber
}

View File

@ -1,35 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert')
const util = require('util')
const parents = require('ldap-filter')
const Filter = require('./filter')
/// --- API
function ApproximateFilter (options) {
parents.ApproximateFilter.call(this, options)
}
util.inherits(ApproximateFilter, parents.ApproximateFilter)
Filter.mixin(ApproximateFilter)
module.exports = ApproximateFilter
ApproximateFilter.prototype.parse = function (ber) {
assert.ok(ber)
this.attribute = ber.readString().toLowerCase()
this.value = ber.readString()
return true
}
ApproximateFilter.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeString(this.attribute)
ber.writeString(this.value)
return ber
}

View File

@ -1,60 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const ASN1 = require('asn1').Ber
const parents = require('ldap-filter')
const Filter = require('./filter')
/// --- API
function EqualityFilter (options) {
parents.EqualityFilter.call(this, options)
}
util.inherits(EqualityFilter, parents.EqualityFilter)
Filter.mixin(EqualityFilter)
module.exports = EqualityFilter
EqualityFilter.prototype.matches = function (target, strictAttrCase) {
assert.object(target, 'target')
const tv = parents.getAttrValue(target, this.attribute, strictAttrCase)
let value = this.value
if (this.attribute.toLowerCase() === 'objectclass') {
/*
* Perform case-insensitive match for objectClass since nearly every LDAP
* implementation behaves in this manner.
*/
value = value.toLowerCase()
return parents.testValues(function (v) {
return value === v.toLowerCase()
}, tv)
} else {
return parents.testValues(function (v) {
return value === v
}, tv)
}
}
EqualityFilter.prototype.parse = function (ber) {
assert.ok(ber)
this.attribute = ber.readString().toLowerCase()
this.value = ber.readString(ASN1.OctetString, true)
if (this.attribute === 'objectclass') { this.value = this.value.toLowerCase() }
return true
}
EqualityFilter.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeString(this.attribute)
ber.writeBuffer(this.raw, ASN1.OctetString)
return ber
}

View File

@ -1,44 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
/**
* RFC 2254 Escaping of filter strings
*
* Raw Escaped
* (o=Parens (R Us)) (o=Parens \28R Us\29)
* (cn=star*) (cn=star\2A)
* (filename=C:\MyFile) (filename=C:\5cMyFile)
*
* Use substr_filter to avoid having * ecsaped.
*
* @author [Austin King](https://github.com/ozten)
*/
exports.escape = function (inp) {
if (typeof (inp) === 'string') {
let esc = ''
for (let i = 0; i < inp.length; i++) {
switch (inp[i]) {
case '*':
esc += '\\2a'
break
case '(':
esc += '\\28'
break
case ')':
esc += '\\29'
break
case '\\':
esc += '\\5c'
break
case '\0':
esc += '\\00'
break
default:
esc += inp[i]
break
}
}
return esc
} else {
return inp
}
}

View File

@ -1,59 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert')
const util = require('util')
const parents = require('ldap-filter')
const Filter = require('./filter')
// THIS IS A STUB!
//
// ldapjs does not support server side extensible matching.
// This class exists only for the client to send them.
/// --- API
function ExtensibleFilter (options) {
parents.ExtensibleFilter.call(this, options)
}
util.inherits(ExtensibleFilter, parents.ExtensibleFilter)
Filter.mixin(ExtensibleFilter)
module.exports = ExtensibleFilter
ExtensibleFilter.prototype.parse = function (ber) {
const end = ber.offset + ber.length
while (ber.offset < end) {
const tag = ber.peek()
switch (tag) {
case 0x81:
this.rule = ber.readString(tag)
break
case 0x82:
this.matchType = ber.readString(tag)
break
case 0x83:
this.value = ber.readString(tag)
break
case 0x84:
this.dnAttributes = ber.readBoolean(tag)
break
default:
throw new Error('Invalid ext_match filter type: 0x' + tag.toString(16))
}
}
return true
}
ExtensibleFilter.prototype._toBer = function (ber) {
assert.ok(ber)
if (this.rule) { ber.writeString(this.rule, 0x81) }
if (this.matchType) { ber.writeString(this.matchType, 0x82) }
ber.writeString(this.value, 0x83)
if (this.dnAttributes) { ber.writeBoolean(this.dnAttributes, 0x84) }
return ber
}

View File

@ -1,60 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
// var assert = require('assert')
const Protocol = require('../protocol')
/// --- Globals
const TYPES = {
and: Protocol.FILTER_AND,
or: Protocol.FILTER_OR,
not: Protocol.FILTER_NOT,
equal: Protocol.FILTER_EQUALITY,
substring: Protocol.FILTER_SUBSTRINGS,
ge: Protocol.FILTER_GE,
le: Protocol.FILTER_LE,
present: Protocol.FILTER_PRESENT,
approx: Protocol.FILTER_APPROX,
ext: Protocol.FILTER_EXT
}
/// --- API
function isFilter (filter) {
if (!filter || typeof (filter) !== 'object') {
return false
}
// Do our best to duck-type it
if (typeof (filter.toBer) === 'function' &&
typeof (filter.matches) === 'function' &&
TYPES[filter.type] !== undefined) {
return true
}
return false
}
function isBerWriter (ber) {
return Boolean(
ber &&
typeof (ber) === 'object' &&
typeof (ber.startSequence) === 'function' &&
typeof (ber.endSequence) === 'function'
)
}
function mixin (target) {
target.prototype.toBer = function toBer (ber) {
if (isBerWriter(ber) === false) { throw new TypeError('ber (BerWriter) required') }
ber.startSequence(TYPES[this.type])
ber = this._toBer(ber)
ber.endSequence()
return ber
}
}
module.exports = {
isFilter: isFilter,
mixin: mixin
}

View File

@ -1,35 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert')
const util = require('util')
const parents = require('ldap-filter')
const Filter = require('./filter')
/// --- API
function GreaterThanEqualsFilter (options) {
parents.GreaterThanEqualsFilter.call(this, options)
}
util.inherits(GreaterThanEqualsFilter, parents.GreaterThanEqualsFilter)
Filter.mixin(GreaterThanEqualsFilter)
module.exports = GreaterThanEqualsFilter
GreaterThanEqualsFilter.prototype.parse = function (ber) {
assert.ok(ber)
this.attribute = ber.readString().toLowerCase()
this.value = ber.readString()
return true
}
GreaterThanEqualsFilter.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeString(this.attribute)
ber.writeString(this.value)
return ber
}

View File

@ -1,208 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert')
const asn1 = require('asn1')
const parents = require('ldap-filter')
const Protocol = require('../protocol')
const Filter = require('./filter')
const AndFilter = require('./and_filter')
const ApproximateFilter = require('./approx_filter')
const EqualityFilter = require('./equality_filter')
const ExtensibleFilter = require('./ext_filter')
const GreaterThanEqualsFilter = require('./ge_filter')
const LessThanEqualsFilter = require('./le_filter')
const NotFilter = require('./not_filter')
const OrFilter = require('./or_filter')
const PresenceFilter = require('./presence_filter')
const SubstringFilter = require('./substr_filter')
/// --- Globals
const BerReader = asn1.BerReader
/// --- Internal Parsers
/*
* A filter looks like this coming in:
* Filter ::= CHOICE {
* and [0] SET OF Filter,
* or [1] SET OF Filter,
* not [2] Filter,
* equalityMatch [3] AttributeValueAssertion,
* substrings [4] SubstringFilter,
* greaterOrEqual [5] AttributeValueAssertion,
* lessOrEqual [6] AttributeValueAssertion,
* present [7] AttributeType,
* approxMatch [8] AttributeValueAssertion,
* extensibleMatch [9] MatchingRuleAssertion --v3 only
* }
*
* SubstringFilter ::= SEQUENCE {
* type AttributeType,
* SEQUENCE OF CHOICE {
* initial [0] IA5String,
* any [1] IA5String,
* final [2] IA5String
* }
* }
*
* The extensibleMatch was added in LDAPv3:
*
* MatchingRuleAssertion ::= SEQUENCE {
* matchingRule [1] MatchingRuleID OPTIONAL,
* type [2] AttributeDescription OPTIONAL,
* matchValue [3] AssertionValue,
* dnAttributes [4] BOOLEAN DEFAULT FALSE
* }
*/
function _parse (ber) {
assert.ok(ber)
function parseSet (f) {
const end = ber.offset + ber.length
while (ber.offset < end) { f.addFilter(_parse(ber)) }
}
let f
const type = ber.readSequence()
switch (type) {
case Protocol.FILTER_AND:
f = new AndFilter()
parseSet(f)
break
case Protocol.FILTER_APPROX:
f = new ApproximateFilter()
f.parse(ber)
break
case Protocol.FILTER_EQUALITY:
f = new EqualityFilter()
f.parse(ber)
return f
case Protocol.FILTER_EXT:
f = new ExtensibleFilter()
f.parse(ber)
return f
case Protocol.FILTER_GE:
f = new GreaterThanEqualsFilter()
f.parse(ber)
return f
case Protocol.FILTER_LE:
f = new LessThanEqualsFilter()
f.parse(ber)
return f
case Protocol.FILTER_NOT:
f = new NotFilter({
filter: _parse(ber)
})
break
case Protocol.FILTER_OR:
f = new OrFilter()
parseSet(f)
break
case Protocol.FILTER_PRESENT:
f = new PresenceFilter()
f.parse(ber)
break
case Protocol.FILTER_SUBSTRINGS:
f = new SubstringFilter()
f.parse(ber)
break
default:
throw new Error('Invalid search filter type: 0x' + type.toString(16))
}
assert.ok(f)
return f
}
function cloneFilter (input) {
let child
if (input.type === 'and' || input.type === 'or') {
child = input.filters.map(cloneFilter)
} else if (input.type === 'not') {
child = cloneFilter(input.filter)
}
switch (input.type) {
case 'and':
return new AndFilter({ filters: child })
case 'or':
return new OrFilter({ filters: child })
case 'not':
return new NotFilter({ filter: child })
case 'equal':
return new EqualityFilter(input)
case 'substring':
return new SubstringFilter(input)
case 'ge':
return new GreaterThanEqualsFilter(input)
case 'le':
return new LessThanEqualsFilter(input)
case 'present':
return new PresenceFilter(input)
case 'approx':
return new ApproximateFilter(input)
case 'ext':
return new ExtensibleFilter(input)
default:
throw new Error('invalid filter type:' + input.type)
}
}
function escapedToHex (str) {
return str.replace(/\\([0-9a-f](?![0-9a-f])|[^0-9a-f]|$)/gi, function (match, p1) {
if (!p1) {
return '\\5c'
}
const hexCode = p1.charCodeAt(0).toString(16)
return '\\' + hexCode
})
}
function parseString (str) {
const hexStr = escapedToHex(str)
const generic = parents.parse(hexStr)
// The filter object(s) return from ldap-filter.parse lack the toBer/parse
// decoration that native ldapjs filter possess. cloneFilter adds that back.
return cloneFilter(generic)
}
/// --- API
module.exports = {
parse: function (ber) {
if (!ber || !(ber instanceof BerReader)) { throw new TypeError('ber (BerReader) required') }
return _parse(ber)
},
parseString: parseString,
isFilter: Filter.isFilter,
AndFilter: AndFilter,
ApproximateFilter: ApproximateFilter,
EqualityFilter: EqualityFilter,
ExtensibleFilter: ExtensibleFilter,
GreaterThanEqualsFilter: GreaterThanEqualsFilter,
LessThanEqualsFilter: LessThanEqualsFilter,
NotFilter: NotFilter,
OrFilter: OrFilter,
PresenceFilter: PresenceFilter,
SubstringFilter: SubstringFilter
}

View File

@ -1,35 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert')
const util = require('util')
const parents = require('ldap-filter')
const Filter = require('./filter')
/// --- API
function LessThanEqualsFilter (options) {
parents.LessThanEqualsFilter.call(this, options)
}
util.inherits(LessThanEqualsFilter, parents.LessThanEqualsFilter)
Filter.mixin(LessThanEqualsFilter)
module.exports = LessThanEqualsFilter
LessThanEqualsFilter.prototype.parse = function (ber) {
assert.ok(ber)
this.attribute = ber.readString().toLowerCase()
this.value = ber.readString()
return true
}
LessThanEqualsFilter.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeString(this.attribute)
ber.writeString(this.value)
return ber
}

View File

@ -1,23 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert')
const util = require('util')
const parents = require('ldap-filter')
const Filter = require('./filter')
/// --- API
function NotFilter (options) {
parents.NotFilter.call(this, options)
}
util.inherits(NotFilter, parents.NotFilter)
Filter.mixin(NotFilter)
module.exports = NotFilter
NotFilter.prototype._toBer = function (ber) {
assert.ok(ber)
return this.filter.toBer(ber)
}

View File

@ -1,27 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert')
const util = require('util')
const parents = require('ldap-filter')
const Filter = require('./filter')
/// --- API
function OrFilter (options) {
parents.OrFilter.call(this, options)
}
util.inherits(OrFilter, parents.OrFilter)
Filter.mixin(OrFilter)
module.exports = OrFilter
OrFilter.prototype._toBer = function (ber) {
assert.ok(ber)
this.filters.forEach(function (f) {
ber = f.toBer(ber)
})
return ber
}

View File

@ -1,36 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert')
const util = require('util')
const parents = require('ldap-filter')
const Filter = require('./filter')
/// --- API
function PresenceFilter (options) {
parents.PresenceFilter.call(this, options)
}
util.inherits(PresenceFilter, parents.PresenceFilter)
Filter.mixin(PresenceFilter)
module.exports = PresenceFilter
PresenceFilter.prototype.parse = function (ber) {
assert.ok(ber)
this.attribute =
ber.buffer.slice(0, ber.length).toString('utf8').toLowerCase()
ber._offset += ber.length
return true
}
PresenceFilter.prototype._toBer = function (ber) {
assert.ok(ber)
for (let i = 0; i < this.attribute.length; i++) { ber.writeByte(this.attribute.charCodeAt(i)) }
return ber
}

View File

@ -1,70 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert')
const util = require('util')
const parents = require('ldap-filter')
const Filter = require('./filter')
/// --- API
function SubstringFilter (options) {
parents.SubstringFilter.call(this, options)
}
util.inherits(SubstringFilter, parents.SubstringFilter)
Filter.mixin(SubstringFilter)
module.exports = SubstringFilter
SubstringFilter.prototype.parse = function (ber) {
assert.ok(ber)
this.attribute = ber.readString().toLowerCase()
ber.readSequence()
const end = ber.offset + ber.length
while (ber.offset < end) {
const tag = ber.peek()
switch (tag) {
case 0x80: // Initial
this.initial = ber.readString(tag)
if (this.attribute === 'objectclass') { this.initial = this.initial.toLowerCase() }
break
case 0x81: { // Any
let anyVal = ber.readString(tag)
if (this.attribute === 'objectclass') { anyVal = anyVal.toLowerCase() }
this.any.push(anyVal)
break
}
case 0x82: // Final
this.final = ber.readString(tag)
if (this.attribute === 'objectclass') { this.final = this.final.toLowerCase() }
break
default:
throw new Error('Invalid substrings filter type: 0x' + tag.toString(16))
}
}
return true
}
SubstringFilter.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeString(this.attribute)
ber.startSequence()
if (this.initial) { ber.writeString(this.initial, 0x80) }
if (this.any && this.any.length) {
this.any.forEach(function (s) {
ber.writeString(s, 0x81)
})
}
if (this.final) { ber.writeString(this.final, 0x82) }
ber.endSequence()
return ber
}

View File

@ -3,16 +3,16 @@
const logger = require('./logger')
const client = require('./client')
const Attribute = require('./attribute')
const Change = require('./change')
const Protocol = require('./protocol')
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('./dn')
const dn = require('@ldapjs/dn')
const errors = require('./errors')
const filters = require('./filters')
const filters = require('@ldapjs/filter')
const messages = require('./messages')
const url = require('./url')
@ -24,7 +24,7 @@ module.exports = {
Client: client.Client,
createClient: client.createClient,
Server: Server,
Server,
createServer: function (options) {
if (options === undefined) { options = {} }
@ -37,21 +37,21 @@ module.exports = {
return new Server(options)
},
Attribute: Attribute,
Change: Change,
Attribute,
Change,
dn: dn,
dn,
DN: dn.DN,
RDN: dn.RDN,
parseDN: dn.parse,
parseDN: dn.DN.fromString,
persistentSearch: persistentSearch,
persistentSearch,
PersistentSearchCache: persistentSearch.PersistentSearchCache,
filters: filters,
filters,
parseFilter: filters.parseString,
url: url,
url,
parseURL: url.parse
}

View File

@ -1,87 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const Protocol = require('../protocol')
/// --- API
function AbandonRequest (options) {
options = options || {}
assert.object(options)
assert.optionalNumber(options.abandonID)
options.protocolOp = Protocol.LDAP_REQ_ABANDON
LDAPMessage.call(this, options)
this.abandonID = options.abandonID || 0
}
util.inherits(AbandonRequest, LDAPMessage)
Object.defineProperties(AbandonRequest.prototype, {
type: {
get: function getType () { return 'AbandonRequest' },
configurable: false
}
})
AbandonRequest.prototype._parse = function (ber, length) {
assert.ok(ber)
assert.ok(length)
// What a PITA - have to replicate ASN.1 integer logic to work around the
// way abandon is encoded and the way ldapjs framework handles "normal"
// messages
const buf = ber.buffer
let offset = 0
let value = 0
const fb = buf[offset++]
value = fb & 0x7F
for (let i = 1; i < length; i++) {
value <<= 8
value |= (buf[offset++] & 0xff)
}
if ((fb & 0x80) === 0x80) { value = -value }
ber._offset += length
this.abandonID = value
return true
}
AbandonRequest.prototype._toBer = function (ber) {
assert.ok(ber)
let i = this.abandonID
let sz = 4
while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000)) &&
(sz > 1)) {
sz--
i <<= 8
}
assert.ok(sz <= 4)
while (sz-- > 0) {
ber.writeByte((i & 0xff000000) >> 24)
i <<= 8
}
return ber
}
AbandonRequest.prototype._json = function (j) {
assert.ok(j)
j.abandonID = this.abandonID
return j
}
/// --- Exports
module.exports = AbandonRequest

View File

@ -1,34 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./result')
// var Protocol = require('../protocol')
/// --- API
function AbandonResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = 0
LDAPMessage.call(this, options)
}
util.inherits(AbandonResponse, LDAPMessage)
Object.defineProperties(AbandonResponse.prototype, {
type: {
get: function getType () { return 'AbandonResponse' },
configurable: false
}
})
AbandonResponse.prototype.end = function (_status) {}
AbandonResponse.prototype._json = function (j) {
return j
}
/// --- Exports
module.exports = AbandonResponse

View File

@ -1,159 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const Attribute = require('../attribute')
const Protocol = require('../protocol')
const lassert = require('../assert')
/// --- API
function AddRequest (options) {
options = options || {}
assert.object(options)
lassert.optionalStringDN(options.entry)
lassert.optionalArrayOfAttribute(options.attributes)
options.protocolOp = Protocol.LDAP_REQ_ADD
LDAPMessage.call(this, options)
this.entry = options.entry || null
this.attributes = options.attributes ? options.attributes.slice(0) : []
}
util.inherits(AddRequest, LDAPMessage)
Object.defineProperties(AddRequest.prototype, {
type: {
get: function getType () { return 'AddRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.entry },
configurable: false
}
})
AddRequest.prototype._parse = function (ber) {
assert.ok(ber)
this.entry = ber.readString()
ber.readSequence()
const end = ber.offset + ber.length
while (ber.offset < end) {
const a = new Attribute()
a.parse(ber)
a.type = a.type.toLowerCase()
if (a.type === 'objectclass') {
for (let i = 0; i < a.vals.length; i++) { a.vals[i] = a.vals[i].toLowerCase() }
}
this.attributes.push(a)
}
this.attributes.sort(Attribute.compare)
return true
}
AddRequest.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeString(this.entry.toString())
ber.startSequence()
this.attributes.forEach(function (a) {
a.toBer(ber)
})
ber.endSequence()
return ber
}
AddRequest.prototype._json = function (j) {
assert.ok(j)
j.entry = this.entry.toString()
j.attributes = []
this.attributes.forEach(function (a) {
j.attributes.push(a.json)
})
return j
}
AddRequest.prototype.indexOf = function (attr) {
if (!attr || typeof (attr) !== 'string') { throw new TypeError('attr (string) required') }
for (let i = 0; i < this.attributes.length; i++) {
if (this.attributes[i].type === attr) { return i }
}
return -1
}
AddRequest.prototype.attributeNames = function () {
const attrs = []
for (let i = 0; i < this.attributes.length; i++) { attrs.push(this.attributes[i].type.toLowerCase()) }
return attrs
}
AddRequest.prototype.getAttribute = function (name) {
if (!name || typeof (name) !== 'string') { throw new TypeError('attribute name (string) required') }
name = name.toLowerCase()
for (let i = 0; i < this.attributes.length; i++) {
if (this.attributes[i].type === name) { return this.attributes[i] }
}
return null
}
AddRequest.prototype.addAttribute = function (attr) {
if (!(attr instanceof Attribute)) { throw new TypeError('attribute (Attribute) required') }
return this.attributes.push(attr)
}
/**
* Returns a "pure" JS representation of this object.
*
* An example object would look like:
*
* {
* "dn": "cn=unit, dc=test",
* "attributes": {
* "cn": ["unit", "foo"],
* "objectclass": ["top", "person"]
* }
* }
*
* @return {Object} that looks like the above.
*/
AddRequest.prototype.toObject = function () {
const self = this
const obj = {
dn: self.entry ? self.entry.toString() : '',
attributes: {}
}
if (!this.attributes || !this.attributes.length) { return obj }
this.attributes.forEach(function (a) {
if (!obj.attributes[a.type]) { obj.attributes[a.type] = [] }
a.vals.forEach(function (v) {
if (obj.attributes[a.type].indexOf(v) === -1) { obj.attributes[a.type].push(v) }
})
})
return obj
}
/// --- Exports
module.exports = AddRequest

View File

@ -1,22 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const Protocol = require('../protocol')
/// --- API
function AddResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.LDAP_REP_ADD
LDAPResult.call(this, options)
}
util.inherits(AddResponse, LDAPResult)
/// --- Exports
module.exports = AddResponse

View File

@ -1,84 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const asn1 = require('asn1')
const LDAPMessage = require('./message')
const Protocol = require('../protocol')
/// --- Globals
const Ber = asn1.Ber
const LDAP_BIND_SIMPLE = 'simple'
// var LDAP_BIND_SASL = 'sasl'
/// --- API
function BindRequest (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.LDAP_REQ_BIND
LDAPMessage.call(this, options)
this.version = options.version || 0x03
this.name = options.name || null
this.authentication = options.authentication || LDAP_BIND_SIMPLE
this.credentials = options.credentials || ''
}
util.inherits(BindRequest, LDAPMessage)
Object.defineProperties(BindRequest.prototype, {
type: {
get: function getType () { return 'BindRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.name },
configurable: false
}
})
BindRequest.prototype._parse = function (ber) {
assert.ok(ber)
this.version = ber.readInt()
this.name = ber.readString()
const t = ber.peek()
// TODO add support for SASL et al
if (t !== Ber.Context) { throw new Error('authentication 0x' + t.toString(16) + ' not supported') }
this.authentication = LDAP_BIND_SIMPLE
this.credentials = ber.readString(Ber.Context)
return true
}
BindRequest.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeInt(this.version)
ber.writeString((this.name || '').toString())
// TODO add support for SASL et al
ber.writeString((this.credentials || ''), Ber.Context)
return ber
}
BindRequest.prototype._json = function (j) {
assert.ok(j)
j.version = this.version
j.name = this.name
j.authenticationType = this.authentication
j.credentials = this.credentials
return j
}
/// --- Exports
module.exports = BindRequest

View File

@ -1,22 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const Protocol = require('../protocol')
/// --- API
function BindResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.LDAP_REP_BIND
LDAPResult.call(this, options)
}
util.inherits(BindResponse, LDAPResult)
/// --- Exports
module.exports = BindResponse

View File

@ -1,74 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const Protocol = require('../protocol')
const lassert = require('../assert')
/// --- API
function CompareRequest (options) {
options = options || {}
assert.object(options)
assert.optionalString(options.attribute)
assert.optionalString(options.value)
lassert.optionalStringDN(options.entry)
options.protocolOp = Protocol.LDAP_REQ_COMPARE
LDAPMessage.call(this, options)
this.entry = options.entry || null
this.attribute = options.attribute || ''
this.value = options.value || ''
}
util.inherits(CompareRequest, LDAPMessage)
Object.defineProperties(CompareRequest.prototype, {
type: {
get: function getType () { return 'CompareRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.entry },
configurable: false
}
})
CompareRequest.prototype._parse = function (ber) {
assert.ok(ber)
this.entry = ber.readString()
ber.readSequence()
this.attribute = ber.readString().toLowerCase()
this.value = ber.readString()
return true
}
CompareRequest.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeString(this.entry.toString())
ber.startSequence()
ber.writeString(this.attribute)
ber.writeString(this.value)
ber.endSequence()
return ber
}
CompareRequest.prototype._json = function (j) {
assert.ok(j)
j.entry = this.entry.toString()
j.attribute = this.attribute
j.value = this.value
return j
}
/// --- Exports
module.exports = CompareRequest

View File

@ -1,33 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const Protocol = require('../protocol')
/// --- API
function CompareResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.LDAP_REP_COMPARE
LDAPResult.call(this, options)
}
util.inherits(CompareResponse, LDAPResult)
CompareResponse.prototype.end = function (matches) {
let status = 0x06
if (typeof (matches) === 'boolean') {
if (!matches) { status = 0x05 } // Compare false
} else {
status = matches
}
return LDAPResult.prototype.end.call(this, status)
}
/// --- Exports
module.exports = CompareResponse

View File

@ -1,62 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const Protocol = require('../protocol')
const lassert = require('../assert')
/// --- API
function DeleteRequest (options) {
options = options || {}
assert.object(options)
lassert.optionalStringDN(options.entry)
options.protocolOp = Protocol.LDAP_REQ_DELETE
LDAPMessage.call(this, options)
this.entry = options.entry || null
}
util.inherits(DeleteRequest, LDAPMessage)
Object.defineProperties(DeleteRequest.prototype, {
type: {
get: function getType () { return 'DeleteRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.entry },
configurable: false
}
})
DeleteRequest.prototype._parse = function (ber, length) {
assert.ok(ber)
this.entry = ber.buffer.slice(0, length).toString('utf8')
ber._offset += ber.length
return true
}
DeleteRequest.prototype._toBer = function (ber) {
assert.ok(ber)
const buf = Buffer.from(this.entry.toString())
for (let i = 0; i < buf.length; i++) { ber.writeByte(buf[i]) }
return ber
}
DeleteRequest.prototype._json = function (j) {
assert.ok(j)
j.entry = this.entry
return j
}
/// --- Exports
module.exports = DeleteRequest

View File

@ -1,22 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const Protocol = require('../protocol')
/// --- API
function DeleteResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.LDAP_REP_DELETE
LDAPResult.call(this, options)
}
util.inherits(DeleteResponse, LDAPResult)
/// --- Exports
module.exports = DeleteResponse

View File

@ -1,117 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const Protocol = require('../protocol')
/// --- API
function ExtendedRequest (options) {
options = options || {}
assert.object(options)
assert.optionalString(options.requestName)
if (options.requestValue &&
!(Buffer.isBuffer(options.requestValue) ||
typeof (options.requestValue) === 'string')) {
throw new TypeError('options.requestValue must be a buffer or a string')
}
options.protocolOp = Protocol.LDAP_REQ_EXTENSION
LDAPMessage.call(this, options)
this.requestName = options.requestName || ''
this.requestValue = options.requestValue
if (Buffer.isBuffer(this.requestValue)) {
this.requestValueBuffer = this.requestValue
} else {
this.requestValueBuffer = Buffer.from(this.requestValue || '', 'utf8')
}
}
util.inherits(ExtendedRequest, LDAPMessage)
Object.defineProperties(ExtendedRequest.prototype, {
type: {
get: function getType () { return 'ExtendedRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.requestName },
configurable: false
},
name: {
get: function getName () { return this.requestName },
set: function setName (val) {
assert.string(val)
this.requestName = val
},
configurable: false
},
value: {
get: function getValue () { return this.requestValue },
set: function setValue (val) {
if (!(Buffer.isBuffer(val) || typeof (val) === 'string')) { throw new TypeError('value must be a buffer or a string') }
if (Buffer.isBuffer(val)) {
this.requestValueBuffer = val
} else {
this.requestValueBuffer = Buffer.from(val, 'utf8')
}
this.requestValue = val
},
configurable: false
},
valueBuffer: {
get: function getValueBuffer () {
return this.requestValueBuffer
},
set: function setValueBuffer (val) {
if (!Buffer.isBuffer(val)) { throw new TypeError('valueBuffer must be a buffer') }
this.value = val
},
configurable: false
}
})
ExtendedRequest.prototype._parse = function (ber) {
assert.ok(ber)
this.requestName = ber.readString(0x80)
if (ber.peek() === 0x81) {
this.requestValueBuffer = ber.readString(0x81, true)
this.requestValue = this.requestValueBuffer.toString('utf8')
}
return true
}
ExtendedRequest.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeString(this.requestName, 0x80)
if (Buffer.isBuffer(this.requestValue)) {
ber.writeBuffer(this.requestValue, 0x81)
} else if (typeof (this.requestValue) === 'string') {
ber.writeString(this.requestValue, 0x81)
}
return ber
}
ExtendedRequest.prototype._json = function (j) {
assert.ok(j)
j.requestName = this.requestName
j.requestValue = (Buffer.isBuffer(this.requestValue))
? this.requestValue.toString('hex')
: this.requestValue
return j
}
/// --- Exports
module.exports = ExtendedRequest

View File

@ -1,86 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const Protocol = require('../protocol')
/// --- API
function ExtendedResponse (options) {
options = options || {}
assert.object(options)
assert.optionalString(options.responseName)
assert.optionalString(options.responsevalue)
this.responseName = options.responseName || undefined
this.responseValue = options.responseValue || undefined
options.protocolOp = Protocol.LDAP_REP_EXTENSION
LDAPResult.call(this, options)
}
util.inherits(ExtendedResponse, LDAPResult)
Object.defineProperties(ExtendedResponse.prototype, {
type: {
get: function getType () { return 'ExtendedResponse' },
configurable: false
},
_dn: {
get: function getDN () { return this.responseName },
configurable: false
},
name: {
get: function getName () { return this.responseName },
set: function setName (val) {
assert.string(val)
this.responseName = val
},
configurable: false
},
value: {
get: function getValue () { return this.responseValue },
set: function (val) {
assert.string(val)
this.responseValue = val
},
configurable: false
}
})
ExtendedResponse.prototype._parse = function (ber) {
assert.ok(ber)
if (!LDAPResult.prototype._parse.call(this, ber)) { return false }
if (ber.peek() === 0x8a) { this.responseName = ber.readString(0x8a) }
if (ber.peek() === 0x8b) { this.responseValue = ber.readString(0x8b) }
return true
}
ExtendedResponse.prototype._toBer = function (ber) {
assert.ok(ber)
if (!LDAPResult.prototype._toBer.call(this, ber)) { return false }
if (this.responseName) { ber.writeString(this.responseName, 0x8a) }
if (this.responseValue) { ber.writeString(this.responseValue, 0x8b) }
return ber
}
ExtendedResponse.prototype._json = function (j) {
assert.ok(j)
j = LDAPResult.prototype._json.call(this, j)
j.responseName = this.responseName
j.responseValue = this.responseValue
return j
}
/// --- Exports
module.exports = ExtendedResponse

View File

@ -1,61 +1,39 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const LDAPMessage = require('./message')
const LDAPResult = require('./result')
const messages = require('@ldapjs/messages')
const Parser = require('./parser')
const AbandonRequest = require('./abandon_request')
const AbandonResponse = require('./abandon_response')
const AddRequest = require('./add_request')
const AddResponse = require('./add_response')
const BindRequest = require('./bind_request')
const BindResponse = require('./bind_response')
const CompareRequest = require('./compare_request')
const CompareResponse = require('./compare_response')
const DeleteRequest = require('./del_request')
const DeleteResponse = require('./del_response')
const ExtendedRequest = require('./ext_request')
const ExtendedResponse = require('./ext_response')
const ModifyRequest = require('./modify_request')
const ModifyResponse = require('./modify_response')
const ModifyDNRequest = require('./moddn_request')
const ModifyDNResponse = require('./moddn_response')
const SearchRequest = require('./search_request')
const SearchEntry = require('./search_entry')
const SearchReference = require('./search_reference')
const SearchResponse = require('./search_response')
const UnbindRequest = require('./unbind_request')
const UnbindResponse = require('./unbind_response')
/// --- API
module.exports = {
LDAPMessage: LDAPMessage,
LDAPResult: LDAPResult,
Parser: Parser,
LDAPMessage: messages.LdapMessage,
LDAPResult: messages.LdapResult,
Parser,
AbandonRequest: AbandonRequest,
AbandonResponse: AbandonResponse,
AddRequest: AddRequest,
AddResponse: AddResponse,
BindRequest: BindRequest,
BindResponse: BindResponse,
CompareRequest: CompareRequest,
CompareResponse: CompareResponse,
DeleteRequest: DeleteRequest,
DeleteResponse: DeleteResponse,
ExtendedRequest: ExtendedRequest,
ExtendedResponse: ExtendedResponse,
ModifyRequest: ModifyRequest,
ModifyResponse: ModifyResponse,
ModifyDNRequest: ModifyDNRequest,
ModifyDNResponse: ModifyDNResponse,
SearchRequest: SearchRequest,
SearchEntry: SearchEntry,
SearchReference: SearchReference,
SearchResponse: SearchResponse,
UnbindRequest: UnbindRequest,
UnbindResponse: UnbindResponse
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
}

View File

@ -1,110 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const asn1 = require('asn1')
const logger = require('../logger')
// var Control = require('../controls').Control
// var Protocol = require('../protocol')
/// --- Globals
// var Ber = asn1.Ber
// var BerReader = asn1.BerReader
const BerWriter = asn1.BerWriter
const getControl = require('../controls').getControl
/// --- API
/**
* LDAPMessage structure.
*
* @param {Object} options stuff.
*/
function LDAPMessage (options) {
assert.object(options)
this.messageID = options.messageID || 0
this.protocolOp = options.protocolOp || undefined
this.controls = options.controls ? options.controls.slice(0) : []
this.log = options.log || logger
}
Object.defineProperties(LDAPMessage.prototype, {
id: {
get: function getId () { return this.messageID },
configurable: false
},
dn: {
get: function getDN () { return this._dn || '' },
configurable: false
},
type: {
get: function getType () { return 'LDAPMessage' },
configurable: false
},
json: {
get: function () {
const out = this._json({
messageID: this.messageID,
protocolOp: this.type
})
out.controls = this.controls
return out
},
configurable: false
}
})
LDAPMessage.prototype.toString = function () {
return JSON.stringify(this.json)
}
LDAPMessage.prototype.parse = function (ber) {
assert.ok(ber)
this.log.trace('parse: data=%s', util.inspect(ber.buffer))
// Delegate off to the specific type to parse
this._parse(ber, ber.length)
// Look for controls
if (ber.peek() === 0xa0) {
ber.readSequence()
const end = ber.offset + ber.length
while (ber.offset < end) {
const c = getControl(ber)
if (c) { this.controls.push(c) }
}
}
this.log.trace('Parsing done: %j', this.json)
return true
}
LDAPMessage.prototype.toBer = function () {
let writer = new BerWriter()
writer.startSequence()
writer.writeInt(this.messageID)
writer.startSequence(this.protocolOp)
if (this._toBer) { writer = this._toBer(writer) }
writer.endSequence()
if (this.controls && this.controls.length) {
writer.startSequence(0xa0)
this.controls.forEach(function (c) {
c.toBer(writer)
})
writer.endSequence()
}
writer.endSequence()
return writer.buffer
}
/// --- Exports
module.exports = LDAPMessage

View File

@ -1,85 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const Protocol = require('../protocol')
const dn = require('../dn')
const lassert = require('../assert')
/// --- API
function ModifyDNRequest (options) {
options = options || {}
assert.object(options)
assert.optionalBool(options.deleteOldRdn)
lassert.optionalStringDN(options.entry)
lassert.optionalDN(options.newRdn)
lassert.optionalDN(options.newSuperior)
options.protocolOp = Protocol.LDAP_REQ_MODRDN
LDAPMessage.call(this, options)
this.entry = options.entry || null
this.newRdn = options.newRdn || null
this.deleteOldRdn = options.deleteOldRdn || true
this.newSuperior = options.newSuperior || null
}
util.inherits(ModifyDNRequest, LDAPMessage)
Object.defineProperties(ModifyDNRequest.prototype, {
type: {
get: function getType () { return 'ModifyDNRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.entry },
configurable: false
}
})
ModifyDNRequest.prototype._parse = function (ber) {
assert.ok(ber)
this.entry = ber.readString()
this.newRdn = dn.parse(ber.readString())
this.deleteOldRdn = ber.readBoolean()
if (ber.peek() === 0x80) { this.newSuperior = dn.parse(ber.readString(0x80)) }
return true
}
ModifyDNRequest.prototype._toBer = function (ber) {
// assert.ok(ber);
ber.writeString(this.entry.toString())
ber.writeString(this.newRdn.toString())
ber.writeBoolean(this.deleteOldRdn)
if (this.newSuperior) {
const s = this.newSuperior.toString()
const len = Buffer.byteLength(s)
ber.writeByte(0x80) // MODIFY_DN_REQUEST_NEW_SUPERIOR_TAG
ber.writeLength(len)
ber._ensure(len)
ber._buf.write(s, ber._offset)
ber._offset += len
}
return ber
}
ModifyDNRequest.prototype._json = function (j) {
assert.ok(j)
j.entry = this.entry.toString()
j.newRdn = this.newRdn.toString()
j.deleteOldRdn = this.deleteOldRdn
j.newSuperior = this.newSuperior ? this.newSuperior.toString() : ''
return j
}
/// --- Exports
module.exports = ModifyDNRequest

View File

@ -1,22 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const Protocol = require('../protocol')
/// --- API
function ModifyDNResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.LDAP_REP_MODRDN
LDAPResult.call(this, options)
}
util.inherits(ModifyDNResponse, LDAPResult)
/// --- Exports
module.exports = ModifyDNResponse

View File

@ -1,83 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const Change = require('../change')
const Protocol = require('../protocol')
const lassert = require('../assert')
/// --- API
function ModifyRequest (options) {
options = options || {}
assert.object(options)
lassert.optionalStringDN(options.object)
lassert.optionalArrayOfAttribute(options.attributes)
options.protocolOp = Protocol.LDAP_REQ_MODIFY
LDAPMessage.call(this, options)
this.object = options.object || null
this.changes = options.changes ? options.changes.slice(0) : []
}
util.inherits(ModifyRequest, LDAPMessage)
Object.defineProperties(ModifyRequest.prototype, {
type: {
get: function getType () { return 'ModifyRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.object },
configurable: false
}
})
ModifyRequest.prototype._parse = function (ber) {
assert.ok(ber)
this.object = ber.readString()
ber.readSequence()
const end = ber.offset + ber.length
while (ber.offset < end) {
const c = new Change()
c.parse(ber)
c.modification.type = c.modification.type.toLowerCase()
this.changes.push(c)
}
this.changes.sort(Change.compare)
return true
}
ModifyRequest.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeString(this.object.toString())
ber.startSequence()
this.changes.forEach(function (c) {
c.toBer(ber)
})
ber.endSequence()
return ber
}
ModifyRequest.prototype._json = function (j) {
assert.ok(j)
j.object = this.object
j.changes = []
this.changes.forEach(function (c) {
j.changes.push(c.json)
})
return j
}
/// --- Exports
module.exports = ModifyRequest

View File

@ -1,22 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const Protocol = require('../protocol')
/// --- API
function ModifyResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.LDAP_REP_MODIFY
LDAPResult.call(this, options)
}
util.inherits(ModifyResponse, LDAPResult)
/// --- Exports
module.exports = ModifyResponse

View File

@ -4,40 +4,36 @@ const EventEmitter = require('events').EventEmitter
const util = require('util')
const assert = require('assert-plus')
const asn1 = require('asn1')
// var VError = require('verror').VError
const asn1 = require('@ldapjs/asn1')
const logger = require('../logger')
const AbandonRequest = require('./abandon_request')
const AddRequest = require('./add_request')
const AddResponse = require('./add_response')
const BindRequest = require('./bind_request')
const BindResponse = require('./bind_response')
const CompareRequest = require('./compare_request')
const CompareResponse = require('./compare_response')
const DeleteRequest = require('./del_request')
const DeleteResponse = require('./del_response')
const ExtendedRequest = require('./ext_request')
const ExtendedResponse = require('./ext_response')
const ModifyRequest = require('./modify_request')
const ModifyResponse = require('./modify_response')
const ModifyDNRequest = require('./moddn_request')
const ModifyDNResponse = require('./moddn_response')
const SearchRequest = require('./search_request')
const SearchEntry = require('./search_entry')
const SearchReference = require('./search_reference')
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 = require('./unbind_request')
// var UnbindResponse = require('./unbind_response')
const UnbindRequest = messages.UnbindRequest
const LDAPResult = messages.LdapResult
const LDAPResult = require('./result')
// var Message = require('./message')
const Protocol = require('../protocol')
const Protocol = require('@ldapjs/protocol')
/// --- Globals
// var Ber = asn1.Ber
const BerReader = asn1.BerReader
/// --- API
@ -52,6 +48,13 @@ function Parser (options = {}) {
}
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') }
@ -64,9 +67,9 @@ Parser.prototype.write = function (data) {
return true
}
self.buffer = (self.buffer ? Buffer.concat([self.buffer, data]) : data)
self.buffer = self.buffer ? Buffer.concat([self.buffer, data]) : data
const ber = new BerReader(self.buffer)
let ber = new BerReader(self.buffer)
let foundSeq = false
try {
@ -80,9 +83,22 @@ Parser.prototype.write = function (data) {
return false
} else if (ber.remain > ber.length) {
// ETOOMUCH
// This is sort of ugly, but allows us to make miminal copies
// 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)
ber._size = 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)
}
@ -92,13 +108,25 @@ Parser.prototype.write = function (data) {
let message
try {
// Bail here if peer isn't speaking protocol at all
message = this.getMessage(ber)
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()
}
message.parse(ber)
// 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
@ -113,88 +141,88 @@ Parser.prototype.getMessage = function (ber) {
const self = this
const messageID = ber.readInt()
const messageId = ber.readInt()
const type = ber.readSequence()
let Message
switch (type) {
case Protocol.LDAP_REQ_ABANDON:
case Protocol.operations.LDAP_REQ_ABANDON:
Message = AbandonRequest
break
case Protocol.LDAP_REQ_ADD:
case Protocol.operations.LDAP_REQ_ADD:
Message = AddRequest
break
case Protocol.LDAP_REP_ADD:
case Protocol.operations.LDAP_RES_ADD:
Message = AddResponse
break
case Protocol.LDAP_REQ_BIND:
case Protocol.operations.LDAP_REQ_BIND:
Message = BindRequest
break
case Protocol.LDAP_REP_BIND:
case Protocol.operations.LDAP_RES_BIND:
Message = BindResponse
break
case Protocol.LDAP_REQ_COMPARE:
case Protocol.operations.LDAP_REQ_COMPARE:
Message = CompareRequest
break
case Protocol.LDAP_REP_COMPARE:
case Protocol.operations.LDAP_RES_COMPARE:
Message = CompareResponse
break
case Protocol.LDAP_REQ_DELETE:
case Protocol.operations.LDAP_REQ_DELETE:
Message = DeleteRequest
break
case Protocol.LDAP_REP_DELETE:
case Protocol.operations.LDAP_RES_DELETE:
Message = DeleteResponse
break
case Protocol.LDAP_REQ_EXTENSION:
case Protocol.operations.LDAP_REQ_EXTENSION:
Message = ExtendedRequest
break
case Protocol.LDAP_REP_EXTENSION:
case Protocol.operations.LDAP_RES_EXTENSION:
Message = ExtendedResponse
break
case Protocol.LDAP_REQ_MODIFY:
case Protocol.operations.LDAP_REQ_MODIFY:
Message = ModifyRequest
break
case Protocol.LDAP_REP_MODIFY:
case Protocol.operations.LDAP_RES_MODIFY:
Message = ModifyResponse
break
case Protocol.LDAP_REQ_MODRDN:
case Protocol.operations.LDAP_REQ_MODRDN:
Message = ModifyDNRequest
break
case Protocol.LDAP_REP_MODRDN:
case Protocol.operations.LDAP_RES_MODRDN:
Message = ModifyDNResponse
break
case Protocol.LDAP_REQ_SEARCH:
case Protocol.operations.LDAP_REQ_SEARCH:
Message = SearchRequest
break
case Protocol.LDAP_REP_SEARCH_ENTRY:
case Protocol.operations.LDAP_RES_SEARCH_ENTRY:
Message = SearchEntry
break
case Protocol.LDAP_REP_SEARCH_REF:
case Protocol.operations.LDAP_RES_SEARCH_REF:
Message = SearchReference
break
case Protocol.LDAP_REP_SEARCH:
case Protocol.operations.LDAP_RES_SEARCH:
Message = SearchResponse
break
case Protocol.LDAP_REQ_UNBIND:
case Protocol.operations.LDAP_REQ_UNBIND:
Message = UnbindRequest
break
@ -203,15 +231,15 @@ Parser.prototype.getMessage = function (ber) {
new Error('Op 0x' + (type ? type.toString(16) : '??') +
' not supported'),
new LDAPResult({
messageID: messageID,
protocolOp: type || Protocol.LDAP_REP_EXTENSION
messageId,
protocolOp: type || Protocol.operations.LDAP_RES_EXTENSION
}))
return false
}
return new Message({
messageID: messageID,
messageId,
log: self.log
})
}

View File

@ -1,121 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
// var asn1 = require('asn1')
const dtrace = require('../dtrace')
const LDAPMessage = require('./message')
const Protocol = require('../protocol')
/// --- Globals
// var Ber = asn1.Ber
// var BerWriter = asn1.BerWriter
/// --- API
function LDAPResult (options) {
options = options || {}
assert.object(options)
assert.optionalNumber(options.status)
assert.optionalString(options.matchedDN)
assert.optionalString(options.errorMessage)
assert.optionalArrayOfString(options.referrals)
LDAPMessage.call(this, options)
this.status = options.status || 0 // LDAP SUCCESS
this.matchedDN = options.matchedDN || ''
this.errorMessage = options.errorMessage || ''
this.referrals = options.referrals || []
this.connection = options.connection || null
}
util.inherits(LDAPResult, LDAPMessage)
Object.defineProperties(LDAPResult.prototype, {
type: {
get: function getType () { return 'LDAPResult' },
configurable: false
}
})
LDAPResult.prototype.end = function (status) {
assert.ok(this.connection)
if (typeof (status) === 'number') { this.status = status }
const ber = this.toBer()
this.log.debug('%s: sending: %j', this.connection.ldap.id, this.json)
try {
const self = this
this.connection.write(ber)
if (self._dtraceOp && self._dtraceId) {
dtrace.fire('server-' + self._dtraceOp + '-done', function () {
const c = self.connection || { ldap: {} }
return [
self._dtraceId || 0,
(c.remoteAddress || ''),
c.ldap.bindDN ? c.ldap.bindDN.toString() : '',
(self.requestDN ? self.requestDN.toString() : ''),
status || self.status,
self.errorMessage
]
})
}
} catch (e) {
this.log.warn(e, '%s failure to write message %j',
this.connection.ldap.id, this.json)
}
}
LDAPResult.prototype._parse = function (ber) {
assert.ok(ber)
this.status = ber.readEnumeration()
this.matchedDN = ber.readString()
this.errorMessage = ber.readString()
const t = ber.peek()
if (t === Protocol.LDAP_REP_REFERRAL) {
const end = ber.offset + ber.length
while (ber.offset < end) { this.referrals.push(ber.readString()) }
}
return true
}
LDAPResult.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeEnumeration(this.status)
ber.writeString(this.matchedDN || '')
ber.writeString(this.errorMessage || '')
if (this.referrals.length) {
ber.startSequence(Protocol.LDAP_REP_REFERRAL)
ber.writeStringArray(this.referrals)
ber.endSequence()
}
return ber
}
LDAPResult.prototype._json = function (j) {
assert.ok(j)
j.status = this.status
j.matchedDN = this.matchedDN
j.errorMessage = this.errorMessage
j.referrals = this.referrals
return j
}
/// --- Exports
module.exports = LDAPResult

View File

@ -1,188 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
// var asn1 = require('asn1')
const LDAPMessage = require('./message')
const Attribute = require('../attribute')
const Protocol = require('../protocol')
const lassert = require('../assert')
/// --- Globals
// var BerWriter = asn1.BerWriter
/// --- API
function SearchEntry (options) {
options = options || {}
assert.object(options)
lassert.optionalStringDN(options.objectName)
options.protocolOp = Protocol.LDAP_REP_SEARCH_ENTRY
LDAPMessage.call(this, options)
this.objectName = options.objectName || null
this.setAttributes(options.attributes || [])
}
util.inherits(SearchEntry, LDAPMessage)
Object.defineProperties(SearchEntry.prototype, {
type: {
get: function getType () { return 'SearchEntry' },
configurable: false
},
_dn: {
get: function getDN () { return this.objectName },
configurable: false
},
object: {
get: function getObject () {
const obj = {
dn: this.dn.toString(),
controls: []
}
this.attributes.forEach(function (a) {
if (a.vals && a.vals.length) {
if (a.vals.length > 1) {
obj[a.type] = a.vals.slice()
} else {
obj[a.type] = a.vals[0]
}
} else {
obj[a.type] = []
}
})
this.controls.forEach(function (element) {
obj.controls.push(element.json)
})
return obj
},
configurable: false
},
raw: {
get: function getRaw () {
const obj = {
dn: this.dn.toString(),
controls: []
}
this.attributes.forEach(function (a) {
if (a.buffers && a.buffers.length) {
if (a.buffers.length > 1) {
obj[a.type] = a.buffers.slice()
} else {
obj[a.type] = a.buffers[0]
}
} else {
obj[a.type] = []
}
})
this.controls.forEach(function (element) {
obj.controls.push(element.json)
})
return obj
},
configurable: false
}
})
SearchEntry.prototype.addAttribute = function (attr) {
if (!attr || typeof (attr) !== 'object') { throw new TypeError('attr (attribute) required') }
this.attributes.push(attr)
}
SearchEntry.prototype.toObject = function () {
return this.object
}
SearchEntry.prototype.fromObject = function (obj) {
if (typeof (obj) !== 'object') { throw new TypeError('object required') }
const self = this
if (obj.controls) { this.controls = obj.controls }
if (obj.attributes) { obj = obj.attributes }
this.attributes = []
Object.keys(obj).forEach(function (k) {
self.attributes.push(new Attribute({ type: k, vals: obj[k] }))
})
return true
}
SearchEntry.prototype.setAttributes = function (obj) {
if (typeof (obj) !== 'object') { throw new TypeError('object required') }
if (Array.isArray(obj)) {
obj.forEach(function (a) {
if (!Attribute.isAttribute(a)) { throw new TypeError('entry must be an Array of Attributes') }
})
this.attributes = obj
} else {
const self = this
self.attributes = []
Object.keys(obj).forEach(function (k) {
const attr = new Attribute({ type: k })
if (Array.isArray(obj[k])) {
obj[k].forEach(function (v) {
attr.addValue(v.toString())
})
} else {
attr.addValue(obj[k].toString())
}
self.attributes.push(attr)
})
}
}
SearchEntry.prototype._json = function (j) {
assert.ok(j)
j.objectName = this.objectName.toString()
j.attributes = []
this.attributes.forEach(function (a) {
j.attributes.push(a.json || a)
})
return j
}
SearchEntry.prototype._parse = function (ber) {
assert.ok(ber)
this.objectName = ber.readString()
assert.ok(ber.readSequence())
const end = ber.offset + ber.length
while (ber.offset < end) {
const a = new Attribute()
a.parse(ber)
this.attributes.push(a)
}
return true
}
SearchEntry.prototype._toBer = function (ber) {
assert.ok(ber)
const formattedObjectName = this.objectName.format({ skipSpace: true })
ber.writeString(formattedObjectName)
ber.startSequence()
this.attributes.forEach(function (a) {
// This may or may not be an attribute
ber = Attribute.toBer(a, ber)
})
ber.endSequence()
return ber
}
/// --- Exports
module.exports = SearchEntry

View File

@ -1,101 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
// var asn1 = require('asn1')
const LDAPMessage = require('./message')
const Protocol = require('../protocol')
const dn = require('../dn')
const url = require('../url')
/// --- Globals
// var BerWriter = asn1.BerWriter
const parseURL = url.parse
/// --- API
function SearchReference (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.LDAP_REP_SEARCH_REF
LDAPMessage.call(this, options)
this.uris = options.uris || []
}
util.inherits(SearchReference, LDAPMessage)
Object.defineProperties(SearchReference.prototype, {
type: {
get: function getType () { return 'SearchReference' },
configurable: false
},
_dn: {
get: function getDN () { return new dn.DN('') },
configurable: false
},
object: {
get: function getObject () {
return {
dn: this.dn.toString(),
uris: this.uris.slice()
}
},
configurable: false
},
urls: {
get: function getUrls () { return this.uris },
set: function setUrls (val) {
assert.ok(val)
assert.ok(Array.isArray(val))
this.uris = val.slice()
},
configurable: false
}
})
SearchReference.prototype.toObject = function () {
return this.object
}
SearchReference.prototype.fromObject = function (obj) {
if (typeof (obj) !== 'object') { throw new TypeError('object required') }
this.uris = obj.uris ? obj.uris.slice() : []
return true
}
SearchReference.prototype._json = function (j) {
assert.ok(j)
j.uris = this.uris.slice()
return j
}
SearchReference.prototype._parse = function (ber, length) {
assert.ok(ber)
while (ber.offset < length) {
const _url = ber.readString()
parseURL(_url)
this.uris.push(_url)
}
return true
}
SearchReference.prototype._toBer = function (ber) {
assert.ok(ber)
this.uris.forEach(function (u) {
ber.writeString(u.href || u)
})
return ber
}
/// --- Exports
module.exports = SearchReference

View File

@ -1,152 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const asn1 = require('asn1')
const LDAPMessage = require('./message')
// var LDAPResult = require('./result')
const dn = require('../dn')
const filters = require('../filters')
const Protocol = require('../protocol')
/// --- Globals
const Ber = asn1.Ber
/// --- API
function SearchRequest (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.LDAP_REQ_SEARCH
LDAPMessage.call(this, options)
if (options.baseObject !== undefined) {
this.baseObject = options.baseObject
} else {
this.baseObject = dn.parse('')
}
this.scope = options.scope || 'base'
this.derefAliases = options.derefAliases || Protocol.NEVER_DEREF_ALIASES
this.sizeLimit = options.sizeLimit || 0
this.timeLimit = options.timeLimit || 0
this.typesOnly = options.typesOnly || false
this.filter = options.filter || null
this.attributes = options.attributes ? options.attributes.slice(0) : []
}
util.inherits(SearchRequest, LDAPMessage)
Object.defineProperties(SearchRequest.prototype, {
type: {
get: function getType () { return 'SearchRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.baseObject },
configurable: false
},
scope: {
get: function getScope () {
switch (this._scope) {
case Protocol.SCOPE_BASE_OBJECT: return 'base'
case Protocol.SCOPE_ONE_LEVEL: return 'one'
case Protocol.SCOPE_SUBTREE: return 'sub'
default:
throw new Error(this._scope + ' is an invalid search scope')
}
},
set: function setScope (val) {
if (typeof (val) === 'string') {
switch (val) {
case 'base':
this._scope = Protocol.SCOPE_BASE_OBJECT
break
case 'one':
this._scope = Protocol.SCOPE_ONE_LEVEL
break
case 'sub':
this._scope = Protocol.SCOPE_SUBTREE
break
default:
throw new Error(val + ' is an invalid search scope')
}
} else {
this._scope = val
}
},
configurable: false
}
})
SearchRequest.prototype._parse = function (ber) {
assert.ok(ber)
this.baseObject = ber.readString()
this.scope = ber.readEnumeration()
this.derefAliases = ber.readEnumeration()
this.sizeLimit = ber.readInt()
this.timeLimit = ber.readInt()
this.typesOnly = ber.readBoolean()
this.filter = filters.parse(ber)
// look for attributes
if (ber.peek() === 0x30) {
ber.readSequence()
const end = ber.offset + ber.length
while (ber.offset < end) { this.attributes.push(ber.readString().toLowerCase()) }
}
return true
}
SearchRequest.prototype._toBer = function (ber) {
assert.ok(ber)
// Format only with commas, since that is what RFC 4514 mandates.
// There's a gotcha here: even though it's called baseObject,
// it can be a string or a DN object.
const formattedDN = dn.DN.isDN(this.baseObject)
? this.baseObject.format({ skipSpace: true })
: this.baseObject.toString()
ber.writeString(formattedDN)
ber.writeEnumeration(this._scope)
ber.writeEnumeration(this.derefAliases)
ber.writeInt(this.sizeLimit)
ber.writeInt(this.timeLimit)
ber.writeBoolean(this.typesOnly)
const f = this.filter || new filters.PresenceFilter({ attribute: 'objectclass' })
ber = f.toBer(ber)
ber.startSequence(Ber.Sequence | Ber.Constructor)
if (this.attributes && this.attributes.length) {
this.attributes.forEach(function (a) {
ber.writeString(a)
})
}
ber.endSequence()
return ber
}
SearchRequest.prototype._json = function (j) {
assert.ok(j)
j.baseObject = this.baseObject
j.scope = this.scope
j.derefAliases = this.derefAliases
j.sizeLimit = this.sizeLimit
j.timeLimit = this.timeLimit
j.typesOnly = this.typesOnly
j.filter = this.filter.toString()
j.attributes = this.attributes
return j
}
/// --- Exports
module.exports = SearchRequest

View File

@ -1,31 +1,31 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const SearchEntry = require('./search_entry')
const SearchReference = require('./search_reference')
const Attribute = require('@ldapjs/attribute')
const {
SearchResultEntry: SearchEntry,
SearchResultReference: SearchReference,
SearchResultDone
} = require('@ldapjs/messages')
const dtrace = require('../dtrace')
const parseDN = require('../dn').parse
const parseURL = require('../url').parse
const Protocol = require('../protocol')
const parseDN = require('@ldapjs/dn').DN.fromString
/// --- API
function SearchResponse (options) {
options = options || {}
assert.object(options)
class SearchResponse extends SearchResultDone {
attributes
notAttributes
sentEntries
options.protocolOp = Protocol.LDAP_REP_SEARCH
LDAPResult.call(this, options)
constructor (options = {}) {
super(options)
this.attributes = options.attributes ? options.attributes.slice() : []
this.notAttributes = []
this.sentEntries = 0
this.attributes = options.attributes ? options.attributes.slice() : []
this.notAttributes = []
this.sentEntries = 0
}
}
util.inherits(SearchResponse, LDAPResult)
/**
* Allows you to send a SearchEntry back to the client.
@ -44,12 +44,16 @@ SearchResponse.prototype.send = function (entry, nofiltering) {
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') }
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] === '_') {
@ -69,39 +73,24 @@ SearchResponse.prototype.send = function (entry, nofiltering) {
save = entry
entry = new SearchEntry({
objectName: typeof (save.dn) === 'string' ? parseDN(save.dn) : save.dn,
messageID: self.messageID,
log: self.log
messageId: self.messageId,
attributes: Attribute.fromObject(entry.attributes)
})
entry.fromObject(save)
}
try {
this.log.debug('%s: sending: %j', this.connection.ldap.id, entry.json)
this.log.debug('%s: sending: %j', this.connection.ldap.id, entry.pojo)
this.connection.write(entry.toBer())
this.connection.write(entry.toBer().buffer)
this.sentEntries++
if (self._dtraceOp && self._dtraceId) {
dtrace.fire('server-search-entry', function () {
const c = self.connection || { ldap: {} }
return [
self._dtraceId || 0,
(c.remoteAddress || ''),
c.ldap.bindDN ? c.ldap.bindDN.toString() : '',
(self.requestDN ? self.requestDN.toString() : ''),
entry.objectName.toString(),
entry.attributes.length
]
})
}
// 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.json)
this.connection.ldap.id, this.pojo)
}
}
@ -109,11 +98,10 @@ SearchResponse.prototype.createSearchEntry = function (object) {
assert.object(object)
const entry = new SearchEntry({
messageID: this.messageID,
log: this.log,
objectName: object.objectName || object.dn
messageId: this.messageId,
objectName: object.objectName || object.dn,
attributes: object.attributes ?? []
})
entry.fromObject((object.attributes || object))
return entry
}
@ -122,15 +110,10 @@ SearchResponse.prototype.createSearchReference = function (uris) {
if (!Array.isArray(uris)) { uris = [uris] }
for (let i = 0; i < uris.length; i++) {
if (typeof (uris[i]) === 'string') { uris[i] = parseURL(uris[i]) }
}
const self = this
return new SearchReference({
messageID: self.messageID,
log: self.log,
uris: uris
messageId: self.messageId,
uri: uris
})
}

View File

@ -1,62 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const dn = require('../dn')
const Protocol = require('../protocol')
/// --- Globals
const DN = dn.DN
const RDN = dn.RDN
/// --- API
function UnbindRequest (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.LDAP_REQ_UNBIND
LDAPMessage.call(this, options)
}
util.inherits(UnbindRequest, LDAPMessage)
Object.defineProperties(UnbindRequest.prototype, {
type: {
get: function getType () { return 'UnbindRequest' },
configurable: false
},
_dn: {
get: function getDN () {
if (this.connection) {
return this.connection.ldap.bindDN
} else {
return new DN([new RDN({ cn: 'anonymous' })])
}
},
configurable: false
}
})
UnbindRequest.prototype._parse = function (ber) {
assert.ok(ber)
return true
}
UnbindRequest.prototype._toBer = function (ber) {
assert.ok(ber)
return ber
}
UnbindRequest.prototype._json = function (j) {
assert.ok(j)
return j
}
/// --- Exports
module.exports = UnbindRequest

View File

@ -1,65 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const dtrace = require('../dtrace')
const LDAPMessage = require('./result')
// var Protocol = require('../protocol')
/// --- API
// Ok, so there's really no such thing as an unbind 'response', but to make
// the framework not suck, I just made this up, and have it stubbed so it's
// not such a one-off.
function UnbindResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = 0
LDAPMessage.call(this, options)
}
util.inherits(UnbindResponse, LDAPMessage)
Object.defineProperties(UnbindResponse.prototype, {
type: {
get: function getType () { return 'UnbindResponse' },
configurable: false
}
})
/**
* Special override that just ends the connection, if present.
*
* @param {Number} _status completely ignored.
*/
UnbindResponse.prototype.end = function (_status) {
assert.ok(this.connection)
this.log.trace('%s: unbinding!', this.connection.ldap.id)
this.connection.end()
const self = this
if (self._dtraceOp && self._dtraceId) {
dtrace.fire('server-' + self._dtraceOp + '-done', function () {
const c = self.connection || { ldap: {} }
return [
self._dtraceId || 0,
(c.remoteAddress || ''),
c.ldap.bindDN ? c.ldap.bindDN.toString() : '',
(self.requestDN ? self.requestDN.toString() : ''),
0,
''
]
})
}
}
UnbindResponse.prototype._json = function (j) {
return j
}
/// --- Exports
module.exports = UnbindResponse

View File

@ -89,7 +89,7 @@ function getEntryChangeNotificationControl (req, obj) {
}
value.changeNumber = attrs.changenumber
return new EntryChangeNotificationControl({ value: value })
return new EntryChangeNotificationControl({ value })
} else {
return false
}
@ -104,6 +104,6 @@ function checkChangeType (req, requestType) {
module.exports = {
PersistentSearchCache: PersistentSearch,
checkChangeType: checkChangeType,
getEntryChangeNotificationControl: getEntryChangeNotificationControl
checkChangeType,
getEntryChangeNotificationControl
}

View File

@ -1,53 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
module.exports = {
// Misc
LDAP_VERSION_3: 0x03,
LBER_SET: 0x31,
LDAP_CONTROLS: 0xa0,
// Search
SCOPE_BASE_OBJECT: 0,
SCOPE_ONE_LEVEL: 1,
SCOPE_SUBTREE: 2,
NEVER_DEREF_ALIASES: 0,
DEREF_IN_SEARCHING: 1,
DEREF_BASE_OBJECT: 2,
DEREF_ALWAYS: 3,
FILTER_AND: 0xa0,
FILTER_OR: 0xa1,
FILTER_NOT: 0xa2,
FILTER_EQUALITY: 0xa3,
FILTER_SUBSTRINGS: 0xa4,
FILTER_GE: 0xa5,
FILTER_LE: 0xa6,
FILTER_PRESENT: 0x87,
FILTER_APPROX: 0xa8,
FILTER_EXT: 0xa9,
// Protocol Operations
LDAP_REQ_BIND: 0x60,
LDAP_REQ_UNBIND: 0x42,
LDAP_REQ_SEARCH: 0x63,
LDAP_REQ_MODIFY: 0x66,
LDAP_REQ_ADD: 0x68,
LDAP_REQ_DELETE: 0x4a,
LDAP_REQ_MODRDN: 0x6c,
LDAP_REQ_COMPARE: 0x6e,
LDAP_REQ_ABANDON: 0x50,
LDAP_REQ_EXTENSION: 0x77,
LDAP_REP_BIND: 0x61,
LDAP_REP_SEARCH_ENTRY: 0x64,
LDAP_REP_SEARCH_REF: 0x73,
LDAP_REP_SEARCH: 0x65,
LDAP_REP_MODIFY: 0x67,
LDAP_REP_ADD: 0x69,
LDAP_REP_DELETE: 0x6b,
LDAP_REP_MODRDN: 0x6d,
LDAP_REP_COMPARE: 0x6f,
LDAP_REP_EXTENSION: 0x78
}

View File

@ -6,33 +6,33 @@ const net = require('net')
const tls = require('tls')
const util = require('util')
// var asn1 = require('asn1')
// var asn1 = require('@ldapjs/asn1')
const VError = require('verror').VError
const dn = require('./dn')
const dtrace = require('./dtrace')
const { DN, RDN } = require('@ldapjs/dn')
const errors = require('./errors')
const Protocol = require('./protocol')
const Protocol = require('@ldapjs/protocol')
const messages = require('@ldapjs/messages')
const Parser = require('./messages').Parser
const AbandonResponse = require('./messages/abandon_response')
const AddResponse = require('./messages/add_response')
const BindResponse = require('./messages/bind_response')
const CompareResponse = require('./messages/compare_response')
const DeleteResponse = require('./messages/del_response')
const ExtendedResponse = require('./messages/ext_response')
// var LDAPResult = require('./messages/result')
const ModifyResponse = require('./messages/modify_response')
const ModifyDNResponse = require('./messages/moddn_response')
const SearchRequest = require('./messages/search_request')
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')
const UnbindResponse = require('./messages/unbind_response')
/// --- Globals
// var Ber = asn1.Ber
// var BerReader = asn1.BerReader
const DN = dn.DN
// const DN = dn.DN
// var sprintf = util.format
@ -47,15 +47,15 @@ function mergeFunctionArgs (argv, start, end) {
const handlers = []
for (let i = start; i < end; i++) {
if (argv[i] instanceof Array) {
if (Array.isArray(argv[i])) {
const arr = argv[i]
for (let j = 0; j < arr.length; j++) {
if (!(arr[j] instanceof Function)) {
if (typeof arr[j] !== 'function') {
throw new TypeError('Invalid argument type: ' + typeof (arr[j]))
}
handlers.push(arr[j])
}
} else if (argv[i] instanceof Function) {
} else if (typeof argv[i] === 'function') {
handlers.push(argv[i])
} else {
throw new TypeError('Invalid argument type: ' + typeof (argv[i]))
@ -71,35 +71,42 @@ function getResponse (req) {
let Response
switch (req.protocolOp) {
case Protocol.LDAP_REQ_BIND:
case Protocol.operations.LDAP_REQ_BIND:
Response = BindResponse
break
case Protocol.LDAP_REQ_ABANDON:
case Protocol.operations.LDAP_REQ_ABANDON:
Response = AbandonResponse
break
case Protocol.LDAP_REQ_ADD:
case Protocol.operations.LDAP_REQ_ADD:
Response = AddResponse
break
case Protocol.LDAP_REQ_COMPARE:
case Protocol.operations.LDAP_REQ_COMPARE:
Response = CompareResponse
break
case Protocol.LDAP_REQ_DELETE:
case Protocol.operations.LDAP_REQ_DELETE:
Response = DeleteResponse
break
case Protocol.LDAP_REQ_EXTENSION:
case Protocol.operations.LDAP_REQ_EXTENSION:
Response = ExtendedResponse
break
case Protocol.LDAP_REQ_MODIFY:
case Protocol.operations.LDAP_REQ_MODIFY:
Response = ModifyResponse
break
case Protocol.LDAP_REQ_MODRDN:
Response = ModifyDNResponse
case Protocol.operations.LDAP_REQ_MODRDN:
Response = ModifyDnResponse
break
case Protocol.LDAP_REQ_SEARCH:
case Protocol.operations.LDAP_REQ_SEARCH:
Response = SearchResponse
break
case Protocol.LDAP_REQ_UNBIND:
Response = UnbindResponse
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
@ -107,16 +114,83 @@ function getResponse (req) {
assert.ok(Response)
const res = new Response({
messageID: req.messageID,
log: req.log,
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)
@ -157,69 +231,6 @@ function noExOpHandler (req, res, next) {
return next()
}
function fireDTraceProbe (req, res) {
assert.ok(req)
req._dtraceId = res._dtraceId = dtrace._nextId()
const probeArgs = [
req._dtraceId,
req.connection.remoteAddress || 'localhost',
req.connection.ldap.bindDN.toString(),
req.dn.toString()
]
let op
switch (req.protocolOp) {
case Protocol.LDAP_REQ_ABANDON:
op = 'abandon'
break
case Protocol.LDAP_REQ_ADD:
op = 'add'
probeArgs.push(req.attributes.length)
break
case Protocol.LDAP_REQ_BIND:
op = 'bind'
break
case Protocol.LDAP_REQ_COMPARE:
op = 'compare'
probeArgs.push(req.attribute)
probeArgs.push(req.value)
break
case Protocol.LDAP_REQ_DELETE:
op = 'delete'
break
case Protocol.LDAP_REQ_EXTENSION:
op = 'exop'
probeArgs.push(req.name)
probeArgs.push(req.value)
break
case Protocol.LDAP_REQ_MODIFY:
op = 'modify'
probeArgs.push(req.changes.length)
break
case Protocol.LDAP_REQ_MODRDN:
op = 'modifydn'
probeArgs.push(req.newRdn.toString())
probeArgs.push((req.newSuperior ? req.newSuperior.toString() : ''))
break
case Protocol.LDAP_REQ_SEARCH:
op = 'search'
probeArgs.push(req.scope)
probeArgs.push(req.filter.toString())
break
case Protocol.LDAP_REQ_UNBIND:
op = 'unbind'
break
default:
break
}
res._dtraceOp = op
dtrace.fire('server-' + op + '-start', function () {
return probeArgs
})
}
/// --- API
/**
@ -261,8 +272,6 @@ function Server (options) {
this._chain = []
this.log = options.log
this.strictDN = (options.strictDN !== undefined) ? options.strictDN : true
const log = this.log
function setupConnection (c) {
@ -277,12 +286,12 @@ function Server (options) {
c.remotePort = c.socket.remotePort
}
const rdn = new dn.RDN({ cn: 'anonymous' })
const rdn = new RDN({ cn: 'anonymous' })
c.ldap = {
id: c.remoteAddress + ':' + c.remotePort,
config: options,
_bindDN: new DN([rdn])
_bindDN: new DN({ rdns: [rdn] })
}
c.addListener('timeout', function () {
log.trace('%s timed out', c.ldap.id)
@ -305,7 +314,9 @@ function Server (options) {
return c.ldap._bindDN
})
c.ldap.__defineSetter__('bindDN', function (val) {
if (!(val instanceof DN)) { throw new TypeError('DN required') }
if (Object.prototype.toString.call(val) !== '[object LdapDn]') {
throw new TypeError('DN required')
}
c.ldap._bindDN = val
return val
@ -319,19 +330,17 @@ function Server (options) {
setupConnection(conn)
log.trace('new connection from %s', conn.ldap.id)
dtrace.fire('server-connection', function () {
return [conn.remoteAddress]
})
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.logId = conn.ldap.id + '::' + req.messageId
req.startTime = new Date().getTime()
log.debug('%s: message received: req=%j', conn.ldap.id, req.json)
log.debug('%s: message received: req=%j', conn.ldap.id, req.pojo)
const res = getResponse(req)
if (!res) {
@ -343,31 +352,47 @@ function Server (options) {
// parse string DNs for routing/etc
try {
switch (req.protocolOp) {
case Protocol.LDAP_REQ_BIND:
req.name = dn.parse(req.name)
case Protocol.operations.LDAP_REQ_BIND: {
req.name = DN.fromString(req.name)
break
case Protocol.LDAP_REQ_ADD:
case Protocol.LDAP_REQ_COMPARE:
case Protocol.LDAP_REQ_DELETE:
req.entry = dn.parse(req.entry)
}
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.LDAP_REQ_MODIFY:
req.object = dn.parse(req.object)
}
case Protocol.operations.LDAP_REQ_MODIFY: {
req.object = DN.fromString(req.object)
break
case Protocol.LDAP_REQ_MODRDN:
req.entry = dn.parse(req.entry)
}
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.LDAP_REQ_SEARCH:
req.baseObject = dn.parse(req.baseObject)
}
case Protocol.operations.LDAP_REQ_SEARCH: {
break
default:
}
default: {
break
}
}
} catch (e) {
if (self.strictDN) {
return res.end(errors.LDAP_INVALID_DN_SYNTAX)
}
return res.end(errors.LDAP_INVALID_DN_SYNTAX)
}
res.connection = conn
@ -406,19 +431,19 @@ function Server (options) {
const next = messageIIFE
if (chain.handlers[i]) { return chain.handlers[i++].call(chain.backend, req, res, next) }
if (req.protocolOp === Protocol.LDAP_REQ_BIND && res.status === 0) {
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([new dn.RDN({ cn: 'anonymous' })])
conn.ldap.bindDN = new DN({ rdns: [new RDN({ cn: 'anonymous' })] })
} else {
conn.ldap.bindDN = req.dn
conn.ldap.bindDN = DN.fromString(req.dn)
}
}
// unbind clear bindDN for safety
// conn should terminate on unbind (RFC4511 4.3)
if (req.protocolOp === Protocol.LDAP_REQ_UNBIND && res.status === 0) {
conn.ldap.bindDN = new DN([new dn.RDN({ cn: 'anonymous' })])
if (req.protocolOp === Protocol.operations.LDAP_REQ_UNBIND && res.status === 0) {
conn.ldap.bindDN = new DN({ rdns: [new RDN({ cn: 'anonymous' })] })
}
return after()
@ -508,7 +533,15 @@ Object.defineProperties(Server.prototype, {
} else {
str = 'ldap://'
}
str += this.host + ':' + this.port
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
@ -528,7 +561,7 @@ module.exports = Server
*/
Server.prototype.add = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.LDAP_REQ_ADD, name, args)
return this._mount(Protocol.operations.LDAP_REQ_ADD, name, args)
}
/**
@ -543,7 +576,7 @@ Server.prototype.add = function (name) {
*/
Server.prototype.bind = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.LDAP_REQ_BIND, name, args)
return this._mount(Protocol.operations.LDAP_REQ_BIND, name, args)
}
/**
@ -558,7 +591,7 @@ Server.prototype.bind = function (name) {
*/
Server.prototype.compare = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.LDAP_REQ_COMPARE, name, args)
return this._mount(Protocol.operations.LDAP_REQ_COMPARE, name, args)
}
/**
@ -573,7 +606,7 @@ Server.prototype.compare = function (name) {
*/
Server.prototype.del = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.LDAP_REQ_DELETE, name, args)
return this._mount(Protocol.operations.LDAP_REQ_DELETE, name, args)
}
/**
@ -588,7 +621,7 @@ Server.prototype.del = function (name) {
*/
Server.prototype.exop = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.LDAP_REQ_EXTENSION, name, args, true)
return this._mount(Protocol.operations.LDAP_REQ_EXTENSION, name, args, true)
}
/**
@ -603,7 +636,7 @@ Server.prototype.exop = function (name) {
*/
Server.prototype.modify = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.LDAP_REQ_MODIFY, name, args)
return this._mount(Protocol.operations.LDAP_REQ_MODIFY, name, args)
}
/**
@ -618,7 +651,7 @@ Server.prototype.modify = function (name) {
*/
Server.prototype.modifyDN = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.LDAP_REQ_MODRDN, name, args)
return this._mount(Protocol.operations.LDAP_REQ_MODRDN, name, args)
}
/**
@ -633,7 +666,7 @@ Server.prototype.modifyDN = function (name) {
*/
Server.prototype.search = function (name) {
const args = Array.prototype.slice.call(arguments, 1)
return this._mount(Protocol.LDAP_REQ_SEARCH, name, args)
return this._mount(Protocol.operations.LDAP_REQ_SEARCH, name, args)
}
/**
@ -647,7 +680,7 @@ Server.prototype.search = function (name) {
*/
Server.prototype.unbind = function () {
const args = Array.prototype.slice.call(arguments, 0)
return this._mount(Protocol.LDAP_REQ_UNBIND, 'unbind', args, true)
return this._mount(Protocol.operations.LDAP_REQ_UNBIND, 'unbind', args, true)
}
Server.prototype.use = function use () {
@ -668,13 +701,13 @@ Server.prototype.after = function () {
})
}
// All these just reexpose the requisite net.Server APIs
// 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 = '0.0.0.0'
host = '127.0.0.1'
}
if (typeof (port) === 'string' && /^[0-9]+$/.test(port)) {
// Disambiguate between string ports and file paths
@ -717,12 +750,10 @@ Server.prototype.getConnections = function (callback) {
}
Server.prototype._getRoute = function (_dn, backend) {
assert.ok(dn)
if (!backend) { backend = this }
let name
if (_dn instanceof dn.DN) {
if (Object.prototype.toString.call(_dn) === '[object LdapDn]') {
name = _dn.toString()
} else {
name = _dn
@ -749,10 +780,10 @@ Server.prototype._sortedRouteKeys = function _sortedRouteKeys () {
Object.keys(this.routes).forEach(function (key) {
const _dn = self.routes[key].dn
// Ignore non-DN routes such as exop or unbind
if (_dn instanceof dn.DN) {
if (Object.prototype.toString.call(_dn) === '[object LdapDn]') {
const reversed = _dn.clone()
reversed.rdns.reverse()
reversedRDNsToKeys[reversed.format()] = key
reversed.reverse()
reversedRDNsToKeys[reversed.toString()] = key
}
})
const output = []
@ -769,17 +800,15 @@ Server.prototype._sortedRouteKeys = function _sortedRouteKeys () {
return this._routeKeyCache
}
Server.prototype._getHandlerChain = function _getHandlerChain (req, res) {
Server.prototype._getHandlerChain = function _getHandlerChain (req) {
assert.ok(req)
fireDTraceProbe(req, res)
const self = this
const routes = this.routes
let route
// check anonymous bind
if (req.protocolOp === Protocol.LDAP_REQ_BIND &&
if (req.protocolOp === Protocol.operations.LDAP_REQ_BIND &&
req.dn.toString() === '' &&
req.credentials === '') {
return {
@ -791,7 +820,7 @@ Server.prototype._getHandlerChain = function _getHandlerChain (req, res) {
const op = '0x' + req.protocolOp.toString(16)
// Special cases are exops, unbinds and abandons. Handle those first.
if (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) {
if (req.protocolOp === Protocol.operations.LDAP_REQ_EXTENSION) {
route = routes[req.requestName]
if (route) {
return {
@ -804,7 +833,7 @@ Server.prototype._getHandlerChain = function _getHandlerChain (req, res) {
handlers: [noExOpHandler]
}
}
} else if (req.protocolOp === Protocol.LDAP_REQ_UNBIND) {
} else if (req.protocolOp === Protocol.operations.LDAP_REQ_UNBIND) {
route = routes.unbind
if (route) {
return {
@ -817,7 +846,7 @@ Server.prototype._getHandlerChain = function _getHandlerChain (req, res) {
handlers: [defaultNoOpHandler]
}
}
} else if (req.protocolOp === Protocol.LDAP_REQ_ABANDON) {
} else if (req.protocolOp === Protocol.operations.LDAP_REQ_ABANDON) {
return {
backend: self,
handlers: [defaultNoOpHandler]
@ -825,11 +854,11 @@ Server.prototype._getHandlerChain = function _getHandlerChain (req, res) {
}
// Otherwise, match via DN rules
assert.ok(req.dn)
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') ? '' : req.dn
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]
@ -876,7 +905,7 @@ Server.prototype._mount = function (op, name, argv, notDN) {
backend = argv[0]
index = 1
}
const route = this._getRoute(notDN ? name : dn.parse(name), backend)
const route = this._getRoute(notDN ? name : DN.fromString(name), backend)
const chain = this._chain.slice()
argv.slice(index).forEach(function (a) {

View File

@ -2,8 +2,8 @@
const querystring = require('querystring')
const url = require('url')
const dn = require('./dn')
const filter = require('./filters/')
const { DN } = require('@ldapjs/dn')
const filter = require('@ldapjs/filter')
module.exports = {
@ -38,7 +38,7 @@ module.exports = {
if (u.pathname) {
u.pathname = querystring.unescape(u.pathname.substr(1))
u.DN = parseDN ? dn.parse(u.pathname) : u.pathname
u.DN = parseDN ? DN.fromString(u.pathname) : u.pathname
}
if (u.search) {

View File

@ -3,57 +3,57 @@
"name": "ldapjs",
"homepage": "http://ldapjs.org",
"description": "LDAP client and server APIs",
"version": "2.3.2",
"version": "3.0.7",
"license": "MIT",
"repository": {
"type": "git",
"url": "git://github.com/ldapjs/node-ldapjs.git"
},
"main": "lib/index.js",
"directories": {
"lib": "./lib"
},
"engines": {
"node": ">=10.13.0"
},
"dependencies": {
"abstract-logging": "^2.0.0",
"asn1": "^0.2.4",
"@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",
"ldap-filter": "^0.3.3",
"once": "^1.4.0",
"vasync": "^2.2.0",
"verror": "^1.8.1"
"vasync": "^2.2.1",
"verror": "^1.10.1"
},
"devDependencies": {
"eslint": "^7.20.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.22.1",
"@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": "^5.1.0",
"eslint-plugin-promise": "6.1.1",
"front-matter": "^4.0.2",
"get-port": "^5.1.1",
"highlight.js": "^11.0.1",
"husky": "^4.2.5",
"marked": "^4.0.0",
"tap": "15.1.6"
"highlight.js": "^11.7.0",
"marked": "^4.2.12",
"tap": "^16.3.7"
},
"scripts": {
"test": "tap --no-cov",
"test:ci": "tap --coverage-report=lcovonly",
"test:cov": "tap",
"test:cov:html": "tap --coverage-report=html",
"test:watch": "tap -n -w --no-coverage-report",
"test:integration": "tap --no-cov 'test-integration/**/*.test.js'",
"test:integration:local": "docker-compose up -d && npm run test:integration && docker-compose down",
"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"
},
"husky": {
"hooks": {
"pre-commit": "npm run lint:ci && npm run test"
}
}
"pre-commit": [
"lint:ci",
"test"
]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,7 +10,14 @@ const PORT = process.env.PORT || 389
const baseURL = `${SCHEME}://${HOST}:${PORT}`
tap.test('modifyDN with long name (issue #480)', t => {
const longStr = 'a292979f2c86d513d48bbb9786b564b3c5228146e5ba46f404724e322544a7304a2b1049168803a5485e2d57a544c6a0d860af91330acb77e5907a9e601ad1227e80e0dc50abe963b47a004f2c90f570450d0e920d15436fdc771e3bdac0487a9735473ed3a79361d1778d7e53a7fb0e5f01f97a75ef05837d1d5496fc86968ff47fcb64'
// 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)
@ -48,9 +55,9 @@ tap.test('whois works correctly (issue #370)', t => {
client.exop('1.3.6.1.4.1.4203.1.11.3', (err, value, res) => {
t.error(err)
t.ok(value)
t.is(value, 'dn:cn=Philip J. Fry,ou=people,dc=planetexpress,dc=com')
t.equal(value, 'dn:cn=Philip J. Fry,ou=people,dc=planetexpress,dc=com')
t.ok(res)
t.is(res.status, 0)
t.equal(res.status, 0)
client.unbind(t.end)
})
@ -74,15 +81,15 @@ tap.test('can access large groups (issue #582)', t => {
})
response.on('error', t.error)
response.on('end', (result) => {
t.is(result.status, 0)
t.is(results.length === 1, true)
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.vals)
t.type(memberAttr.vals, Array)
t.is(memberAttr.vals.length, 2000)
t.ok(memberAttr.values)
t.type(memberAttr.values, Array)
t.equal(memberAttr.values.length, 2000)
client.unbind(t.end)
})

View File

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

View File

@ -1,163 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('asn1')
const { Attribute } = require('../lib')
test('new no args', function (t) {
t.ok(new Attribute())
t.end()
})
test('new with args', function (t) {
let attr = new Attribute({
type: 'cn',
vals: ['foo', 'bar']
})
t.ok(attr)
attr.addValue('baz')
t.equal(attr.type, 'cn')
t.equal(attr.vals.length, 3)
t.equal(attr.vals[0], 'foo')
t.equal(attr.vals[1], 'bar')
t.equal(attr.vals[2], 'baz')
t.throws(function () {
attr = new Attribute('not an object')
})
t.throws(function () {
const typeThatIsNotAString = 1
attr = new Attribute({
type: typeThatIsNotAString
})
})
t.end()
})
test('toBer', function (t) {
const attr = new Attribute({
type: 'cn',
vals: ['foo', 'bar']
})
t.ok(attr)
const ber = new BerWriter()
attr.toBer(ber)
const reader = new BerReader(ber.buffer)
t.ok(reader.readSequence())
t.equal(reader.readString(), 'cn')
t.equal(reader.readSequence(), 0x31) // lber set
t.equal(reader.readString(), 'foo')
t.equal(reader.readString(), 'bar')
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.startSequence()
ber.writeString('cn')
ber.startSequence(0x31)
ber.writeStringArray(['foo', 'bar'])
ber.endSequence()
ber.endSequence()
const attr = new Attribute()
t.ok(attr)
t.ok(attr.parse(new BerReader(ber.buffer)))
t.equal(attr.type, 'cn')
t.equal(attr.vals.length, 2)
t.equal(attr.vals[0], 'foo')
t.equal(attr.vals[1], 'bar')
t.end()
})
test('parse - without 0x31', function (t) {
const ber = new BerWriter()
ber.startSequence()
ber.writeString('sn')
ber.endSequence()
const attr = new Attribute()
t.ok(attr)
t.ok(attr.parse(new BerReader(ber.buffer)))
t.equal(attr.type, 'sn')
t.equal(attr.vals.length, 0)
t.end()
})
test('toString', function (t) {
const attr = new Attribute({
type: 'foobar',
vals: ['asdf']
})
const expected = attr.toString()
const actual = JSON.stringify(attr.json)
t.equal(actual, expected)
t.end()
})
test('isAttribute', function (t) {
const isA = Attribute.isAttribute
t.notOk(isA(null))
t.notOk(isA('asdf'))
t.ok(isA(new Attribute({
type: 'foobar',
vals: ['asdf']
})))
t.ok(isA({
type: 'foo',
vals: ['item', Buffer.alloc(5)],
toBer: function () { /* placeholder */ }
}))
// bad type in vals
t.notOk(isA({
type: 'foo',
vals: ['item', null],
toBer: function () { /* placeholder */ }
}))
t.end()
})
test('compare', function (t) {
const comp = Attribute.compare
const a = new Attribute({
type: 'foo',
vals: ['bar']
})
const b = new Attribute({
type: 'foo',
vals: ['bar']
})
const notAnAttribute = 'this is not an attribute'
t.throws(function () {
comp(a, notAnAttribute)
})
t.throws(function () {
comp(notAnAttribute, b)
})
t.equal(comp(a, b), 0)
// Different types
a.type = 'boo'
t.equal(comp(a, b), -1)
t.equal(comp(b, a), 1)
a.type = 'foo'
// Different value counts
a.vals = ['bar', 'baz']
t.equal(comp(a, b), 1)
t.equal(comp(b, a), -1)
// Different value contents (same count)
a.vals = ['baz']
t.equal(comp(a, b), 1)
t.equal(comp(b, a), -1)
t.end()
})

View File

@ -1,253 +0,0 @@
'use strict'
const fs = require('fs')
const path = require('path')
const { test } = require('tap')
const { BerReader, BerWriter } = require('asn1')
const { Attribute, Change } = require('../lib')
test('new no args', function (t) {
t.ok(new Change())
t.end()
})
test('new with args', function (t) {
const change = new Change({
operation: 'add',
modification: new Attribute({
type: 'cn',
vals: ['foo', 'bar']
})
})
t.ok(change)
t.equal(change.operation, 'add')
t.equal(change.modification.type, 'cn')
t.equal(change.modification.vals.length, 2)
t.equal(change.modification.vals[0], 'foo')
t.equal(change.modification.vals[1], 'bar')
t.end()
})
test('new with args and buffer', function (t) {
const img = fs.readFileSync(path.join(__dirname, '/imgs/test.jpg'))
const change = new Change({
operation: 'add',
modification: {
thumbnailPhoto: img
}
})
t.ok(change)
t.equal(change.operation, 'add')
t.equal(change.modification.type, 'thumbnailPhoto')
t.equal(change.modification.vals.length, 1)
t.equal(change.modification.buffers[0].compare(img), 0)
t.end()
})
test('validate fields', function (t) {
const c = new Change()
t.ok(c)
t.throws(function () {
c.operation = 'bogus'
})
t.throws(function () {
c.modification = { too: 'many', fields: 'here' }
})
c.modification = {
foo: ['bar', 'baz']
}
t.ok(c.modification)
t.end()
})
test('GH-31 (multiple attributes per Change)', function (t) {
t.throws(function () {
const c = new Change({
operation: 'replace',
modification: {
cn: 'foo',
sn: 'bar'
}
})
t.notOk(c)
})
t.end()
})
test('toBer', function (t) {
const change = new Change({
operation: 'Add',
modification: new Attribute({
type: 'cn',
vals: ['foo', 'bar']
})
})
t.ok(change)
const ber = new BerWriter()
change.toBer(ber)
const reader = new BerReader(ber.buffer)
t.ok(reader.readSequence())
t.equal(reader.readEnumeration(), 0x00)
t.ok(reader.readSequence())
t.equal(reader.readString(), 'cn')
t.equal(reader.readSequence(), 0x31) // lber set
t.equal(reader.readString(), 'foo')
t.equal(reader.readString(), 'bar')
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.startSequence()
ber.writeEnumeration(0x00)
ber.startSequence()
ber.writeString('cn')
ber.startSequence(0x31)
ber.writeStringArray(['foo', 'bar'])
ber.endSequence()
ber.endSequence()
ber.endSequence()
const change = new Change()
t.ok(change)
t.ok(change.parse(new BerReader(ber.buffer)))
t.equal(change.operation, 'add')
t.equal(change.modification.type, 'cn')
t.equal(change.modification.vals.length, 2)
t.equal(change.modification.vals[0], 'foo')
t.equal(change.modification.vals[1], 'bar')
t.end()
})
test('apply - replace', function (t) {
let res
const single = new Change({
operation: 'replace',
modification: {
type: 'cn',
vals: ['new']
}
})
const twin = new Change({
operation: 'replace',
modification: {
type: 'cn',
vals: ['new', 'two']
}
})
const empty = new Change({
operation: 'replace',
modification: {
type: 'cn',
vals: []
}
})
// plain
res = Change.apply(single, { cn: ['old'] })
t.same(res.cn, ['new'])
// multiple
res = Change.apply(single, { cn: ['old', 'also'] })
t.same(res.cn, ['new'])
// empty
res = Change.apply(empty, { cn: ['existing'] })
t.equal(res.cn, undefined)
t.ok(Object.keys(res).indexOf('cn') === -1)
// absent
res = Change.apply(single, { dn: ['otherjunk'] })
t.same(res.cn, ['new'])
// scalar formatting "success"
res = Change.apply(single, { cn: 'old' }, true)
t.equal(res.cn, 'new')
// scalar formatting "failure"
res = Change.apply(twin, { cn: 'old' }, true)
t.same(res.cn, ['new', 'two'])
t.end()
})
test('apply - add', function (t) {
let res
const single = new Change({
operation: 'add',
modification: {
type: 'cn',
vals: ['new']
}
})
// plain
res = Change.apply(single, { cn: ['old'] })
t.same(res.cn, ['old', 'new'])
// multiple
res = Change.apply(single, { cn: ['old', 'also'] })
t.same(res.cn, ['old', 'also', 'new'])
// absent
res = Change.apply(single, { dn: ['otherjunk'] })
t.same(res.cn, ['new'])
// scalar formatting "success"
res = Change.apply(single, { }, true)
t.equal(res.cn, 'new')
// scalar formatting "failure"
res = Change.apply(single, { cn: 'old' }, true)
t.same(res.cn, ['old', 'new'])
// duplicate add
res = Change.apply(single, { cn: 'new' })
t.same(res.cn, ['new'])
t.end()
})
test('apply - delete', function (t) {
let res
const single = new Change({
operation: 'delete',
modification: {
type: 'cn',
vals: ['old']
}
})
// plain
res = Change.apply(single, { cn: ['old', 'new'] })
t.same(res.cn, ['new'])
// empty
res = Change.apply(single, { cn: ['old'] })
t.equal(res.cn, undefined)
t.ok(Object.keys(res).indexOf('cn') === -1)
// scalar formatting "success"
res = Change.apply(single, { cn: ['old', 'one'] }, true)
t.equal(res.cn, 'one')
// scalar formatting "failure"
res = Change.apply(single, { cn: ['old', 'several', 'items'] }, true)
t.same(res.cn, ['several', 'items'])
// absent
res = Change.apply(single, { dn: ['otherjunk'] })
t.ok(res)
t.equal(res.cn, undefined)
t.end()
})

View File

@ -6,8 +6,19 @@ const tap = require('tap')
const vasync = require('vasync')
const getPort = require('get-port')
const { getSock, uuid } = require('./utils')
const Attribute = require('@ldapjs/attribute')
const Change = require('@ldapjs/change')
const messages = require('@ldapjs/messages')
const controls = require('@ldapjs/controls')
const dn = require('@ldapjs/dn')
const ldap = require('../lib')
const { Attribute, Change } = ldap
const {
SearchRequest,
SearchResultEntry,
SearchResultReference,
SearchResultDone
} = messages
const SUFFIX = 'dc=test'
const LDAP_CONNECT_TIMEOUT = process.env.LDAP_CONNECT_TIMEOUT || 0
@ -44,7 +55,7 @@ tap.beforeEach((t) => {
// LDAP whoami
server.exop('1.3.6.1.4.1.4203.1.11.3', function (req, res, next) {
res.value = 'u:xxyyz@EXAMPLE.NET'
res.responseValue = 'u:xxyyz@EXAMPLE.NET'
res.end()
return next()
})
@ -88,21 +99,21 @@ tap.beforeEach((t) => {
if (req.dn.equals('cn=ref,' + SUFFIX)) {
res.send(res.createSearchReference('ldap://localhost'))
} else if (req.dn.equals('cn=bin,' + SUFFIX)) {
const attributes = []
attributes.push(new Attribute({ type: 'foo;binary', values: ['wr0gKyDCvCA9IMK+'] }))
attributes.push(new Attribute({ type: 'gb18030', values: [Buffer.from([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA])] }))
attributes.push(new Attribute({ type: 'objectclass', values: ['binary'] }))
res.send(res.createSearchEntry({
objectName: req.dn,
attributes: {
'foo;binary': 'wr0gKyDCvCA9IMK+',
gb18030: Buffer.from([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA]),
objectclass: 'binary'
}
attributes
}))
} else {
const attributes = []
attributes.push(new Attribute({ type: 'cn', values: ['unit', 'test'] }))
attributes.push(new Attribute({ type: 'SN', values: ['testy'] }))
const e = res.createSearchEntry({
objectName: req.dn,
attributes: {
cn: ['unit', 'test'],
SN: 'testy'
}
attributes
})
res.send(e)
res.send(e)
@ -142,13 +153,14 @@ tap.beforeEach((t) => {
end = (end > max || end < min) ? max : end
let i
for (i = start; i < end; i++) {
res.send({
dn: util.format('o=%d, cn=paged', i),
attributes: {
res.send(new SearchResultEntry({
messageId: res.id,
entry: `o=${i},cn=paged`,
attributes: Attribute.fromObject({
o: [i],
objectclass: ['pagedResult']
}
})
})
}))
}
return i
}
@ -156,39 +168,40 @@ tap.beforeEach((t) => {
let cookie = null
let pageSize = 0
req.controls.forEach(function (control) {
if (control.type === ldap.PagedResultsControl.OID) {
if (control.type === controls.PagedResultsControl.OID) {
pageSize = control.value.size
cookie = control.value.cookie
}
})
if (cookie && Buffer.isBuffer(cookie)) {
// Do simple paging
let first = min
if (cookie.length !== 0) {
first = parseInt(cookie.toString(), 10)
}
const last = sendResults(first, first + pageSize)
let resultCookie
if (last < max) {
resultCookie = Buffer.from(last.toString())
} else {
resultCookie = Buffer.from('')
}
res.controls.push(new ldap.PagedResultsControl({
value: {
size: pageSize, // correctness not required here
cookie: resultCookie
}
}))
res.end()
next()
} else {
if (!cookie || Buffer.isBuffer(cookie) === false) {
// don't allow non-paged searches for this test endpoint
next(new ldap.UnwillingToPerformError())
next(Error('unwilling to perform'))
}
// Do simple paging
let first = min
if (cookie.length !== 0) {
first = parseInt(cookie.toString(), 10)
}
const last = sendResults(first, first + pageSize)
let resultCookie
if (last < max) {
resultCookie = Buffer.from(last.toString())
} else {
resultCookie = Buffer.from('')
}
res.addControl(new controls.PagedResultsControl({
value: {
size: pageSize, // correctness not required here
cookie: resultCookie
}
}))
res.end()
next()
})
server.search('cn=sssvlv', function (req, res, next) {
const min = 0
const max = 100
@ -484,7 +497,7 @@ tap.test('add success', function (t) {
const attrs = [
new Attribute({
type: 'cn',
vals: ['test']
values: ['test']
})
]
t.context.client.add('cn=add, ' + SUFFIX, attrs, function (err, res) {
@ -509,8 +522,8 @@ tap.test('add success with object', function (t) {
})
tap.test('add buffer', function (t) {
const { BerReader } = require('asn1')
const dn = `cn=add, ${SUFFIX}`
const { BerReader } = require('@ldapjs/asn1')
const dn = `cn=add,${SUFFIX}`
const attribute = 'thumbnailPhoto'
const binary = 0xa5
const entry = {
@ -519,7 +532,7 @@ tap.test('add buffer', function (t) {
const write = t.context.client._socket.write
t.context.client._socket.write = (data, encoding, cb) => {
const reader = new BerReader(data)
t.equal(data.byteLength, 49)
t.equal(data.byteLength, 48)
t.ok(reader.readSequence())
t.equal(reader.readInt(), 0x1)
t.equal(reader.readSequence(), 0x68)
@ -621,7 +634,7 @@ tap.test('modify success', function (t) {
type: 'Replace',
modification: new Attribute({
type: 'cn',
vals: ['test']
values: ['test']
})
})
t.context.client.modify('cn=modify, ' + SUFFIX, change, function (err, res) {
@ -632,26 +645,11 @@ tap.test('modify success', function (t) {
})
})
tap.test('modify change plain object success', function (t) {
const change = new Change({
type: 'Replace',
modification: {
cn: 'test'
}
})
t.context.client.modify('cn=modify, ' + SUFFIX, change, function (err, res) {
t.error(err)
t.ok(res)
t.equal(res.status, 0)
t.end()
})
})
// https://github.com/ldapjs/node-ldapjs/pull/435
tap.test('can delete attributes', function (t) {
const change = new Change({
type: 'Delete',
modification: { cn: null }
modification: new Attribute({ type: 'cn', values: [null] })
})
t.context.client.modify('cn=modify,' + SUFFIX, change, function (err, res) {
t.error(err)
@ -667,7 +665,7 @@ tap.test('modify array success', function (t) {
operation: 'Replace',
modification: new Attribute({
type: 'cn',
vals: ['test']
values: ['test']
})
}),
new Change({
@ -685,22 +683,6 @@ tap.test('modify array success', function (t) {
})
})
tap.test('modify change plain object success (GH-31)', function (t) {
const change = {
type: 'replace',
modification: {
cn: 'test',
sn: 'bar'
}
}
t.context.client.modify('cn=modify, ' + SUFFIX, change, function (err, res) {
t.error(err)
t.ok(res)
t.equal(res.status, 0)
t.end()
})
})
tap.test('modify DN new RDN only', function (t) {
t.context.client.modifyDN('cn=old, ' + SUFFIX, 'cn=new', function (err, res) {
t.error(err)
@ -729,47 +711,42 @@ tap.test('modify DN excessive length (GH-480)', function (t) {
})
tap.test('modify DN excessive superior length', function (t) {
const { BerReader, BerWriter } = require('asn1')
const ModifyDNRequest = require('../lib/messages/moddn_request')
const ber = new BerWriter()
const { ModifyDnRequest } = messages
const entry = 'cn=Test User,ou=A Long OU ,ou=Another Long OU ,ou=Another Long OU ,dc=acompany,DC=io'
const newSuperior = 'ou=A New Long OU , ou=Another New Long OU , ou=An OU , dc=acompany, dc=io'
const newRdn = entry.replace(/(.*?),.*/, '$1')
const deleteOldRdn = true
const req = new ModifyDNRequest({
entry: entry,
deleteOldRdn: deleteOldRdn,
controls: []
const req = new ModifyDnRequest({
entry,
deleteOldRdn,
newRdn,
newSuperior
})
req.newRdn = newRdn
req.newSuperior = newSuperior
req._toBer(ber)
const reader = new BerReader(ber.buffer)
t.equal(reader.readString(), entry)
t.equal(reader.readString(), newRdn)
t.equal(reader.readBoolean(), deleteOldRdn)
t.equal(reader.readByte(), 0x80)
reader.readLength()
t.equal(reader._len, newSuperior.length)
reader._buf[--reader._offset] = 0x4
t.equal(reader.readString(), newSuperior)
t.equal(req.entry.toString(), 'cn=Test User,ou=A Long OU,ou=Another Long OU,ou=Another Long OU,dc=acompany,DC=io')
t.equal(req.newRdn.toString(), 'cn=Test User')
t.equal(req.deleteOldRdn, true)
t.equal(req.newSuperior.toString(), 'ou=A New Long OU,ou=Another New Long OU,ou=An OU,dc=acompany,dc=io')
t.end()
})
tap.test('search basic', function (t) {
const { SearchResultEntry, SearchResultDone } = messages
t.context.client.search('cn=test, ' + SUFFIX, '(objectclass=*)', function (err, res) {
t.error(err)
t.ok(res)
let gotEntry = 0
res.on('searchEntry', function (entry) {
t.ok(entry)
t.ok(entry instanceof ldap.SearchEntry)
t.ok(entry instanceof SearchResultEntry)
t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
t.ok(entry.attributes)
t.ok(entry.attributes.length)
t.equal(entry.attributes[0].type, 'cn')
t.equal(entry.attributes[1].type, 'SN')
t.ok(entry.object)
gotEntry++
})
res.on('error', function (err) {
@ -777,7 +754,37 @@ tap.test('search basic', function (t) {
})
res.on('end', function (res) {
t.ok(res)
t.ok(res instanceof ldap.SearchResponse)
t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0)
t.equal(gotEntry, 2)
t.end()
})
})
})
tap.test('search basic with DN', function (t) {
const { SearchResultEntry, SearchResultDone } = messages
t.context.client.search(dn.DN.fromString('cn=test, ' + SUFFIX, '(objectclass=*)'), function (err, res) {
t.error(err)
t.ok(res)
let gotEntry = 0
res.on('searchEntry', function (entry) {
t.ok(entry)
t.ok(entry instanceof SearchResultEntry)
t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
t.ok(entry.attributes)
t.ok(entry.attributes.length)
t.equal(entry.attributes[0].type, 'cn')
t.equal(entry.attributes[1].type, 'SN')
gotEntry++
})
res.on('error', function (err) {
t.fail(err)
})
res.on('end', function (res) {
t.ok(res)
t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0)
t.equal(gotEntry, 2)
t.end()
@ -844,7 +851,7 @@ tap.test('search paged', { timeout: 10000 }, function (t) {
t2.error(err)
res.on('searchEntry', entryListener)
res.on('searchRequest', (searchRequest) => {
t2.ok(searchRequest instanceof ldap.SearchRequest)
t2.ok(searchRequest instanceof SearchRequest)
if (currentSearchRequest === null) {
t2.equal(countPages, 0)
}
@ -855,7 +862,7 @@ tap.test('search paged', { timeout: 10000 }, function (t) {
res.on('end', function (result) {
t2.equal(countEntries, 1000)
t2.equal(countPages, 10)
t2.equal(result.messageID, currentSearchRequest.messageID)
t2.equal(result.messageId, currentSearchRequest.messageId)
t2.end()
})
@ -871,7 +878,7 @@ tap.test('search paged', { timeout: 10000 }, function (t) {
function pageListener (result) {
countPages += 1
if (countPages < 10) {
t2.equal(result.messageID, currentSearchRequest.messageID)
t2.equal(result.messageId, currentSearchRequest.messageId)
}
}
})
@ -897,7 +904,7 @@ tap.test('search paged', { timeout: 10000 }, function (t) {
countPages++
// cancel after 9 to verify callback usage
if (countPages === 9) {
// another page should never be encountered
// another page should never be encountered
res.removeListener('page', pageListener)
.on('page', t2.fail.bind(null, 'unexpected page'))
return cb(new Error())
@ -995,7 +1002,12 @@ tap.test('search paged', { timeout: 10000 }, function (t) {
t.end()
})
tap.test('search - sssvlv', { timeout: 10000 }, function (t) {
// We are skipping the ServerSideSorting test because we have skipped
// properly implementing the controls in order to get v3 shipped. These
// tests should be re-enabled once we have addressed this issue.
// ~ jsumners 2023-02-19
// TODO: re-enable after adding back SSSR support
tap.test('search - sssvlv', { timeout: 10000, skip: true }, function (t) {
t.test('ssv - asc', function (t2) {
let preventry = null
const sssrcontrol = new ldap.ServerSideSortingRequestControl(
@ -1025,6 +1037,7 @@ tap.test('search - sssvlv', { timeout: 10000 }, function (t) {
})
})
})
t.test('ssv - desc', function (t2) {
let preventry = null
const sssrcontrol = new ldap.ServerSideSortingRequestControl(
@ -1055,7 +1068,9 @@ tap.test('search - sssvlv', { timeout: 10000 }, function (t) {
})
})
t.test('vlv - first page', function (t2) {
t.test('vlv - first page', { skip: true }, function (t2) {
// This test is disabled.
// See https://github.com/ldapjs/node-ldapjs/pull/797#issuecomment-1094132289
const sssrcontrol = new ldap.ServerSideSortingRequestControl(
{
value: {
@ -1097,7 +1112,10 @@ tap.test('search - sssvlv', { timeout: 10000 }, function (t) {
})
})
})
t.test('vlv - last page', function (t2) {
t.test('vlv - last page', { skip: true }, function (t2) {
// This test is disabled.
// See https://github.com/ldapjs/node-ldapjs/pull/797#issuecomment-1094132289
const sssrcontrol = new ldap.ServerSideSortingRequestControl(
{
value: {
@ -1139,6 +1157,7 @@ tap.test('search - sssvlv', { timeout: 10000 }, function (t) {
})
})
})
t.end()
})
@ -1154,7 +1173,7 @@ tap.test('search referral', function (t) {
res.on('searchReference', function (referral) {
gotReferral = true
t.ok(referral)
t.ok(referral instanceof ldap.SearchReference)
t.ok(referral instanceof SearchResultReference)
t.ok(referral.uris)
t.ok(referral.uris.length)
})
@ -1163,7 +1182,7 @@ tap.test('search referral', function (t) {
})
res.on('end', function (res) {
t.ok(res)
t.ok(res instanceof ldap.SearchResponse)
t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0)
t.equal(gotEntry, 0)
t.ok(gotReferral)
@ -1180,14 +1199,13 @@ tap.test('search rootDSE', function (t) {
t.ok(entry)
t.equal(entry.dn.toString(), '')
t.ok(entry.attributes)
t.ok(entry.object)
})
res.on('error', function (err) {
t.fail(err)
})
res.on('end', function (res) {
t.ok(res)
t.ok(res instanceof ldap.SearchResponse)
t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0)
t.end()
})
@ -1200,12 +1218,16 @@ tap.test('search empty attribute', function (t) {
t.ok(res)
let gotEntry = 0
res.on('searchEntry', function (entry) {
const obj = entry.toObject()
t.equal('dc=empty', obj.dn)
t.ok(obj.member)
t.equal(obj.member.length, 0)
t.ok(obj['member;range=0-1'])
t.ok(obj['member;range=0-1'].length)
const obj = entry.pojo
t.equal('dc=empty', obj.objectName)
const member = entry.attributes[0]
t.ok(member)
t.equal(member.values.length, 0)
const rangedMember = entry.attributes[1]
t.equal(rangedMember.type, 'member;range=0-1')
t.equal(rangedMember.values.length, 2)
gotEntry++
})
res.on('error', function (err) {
@ -1213,7 +1235,7 @@ tap.test('search empty attribute', function (t) {
})
res.on('end', function (res) {
t.ok(res)
t.ok(res instanceof ldap.SearchResponse)
t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0)
t.equal(gotEntry, 1)
t.end()
@ -1230,12 +1252,12 @@ tap.test('GH-21 binary attributes', function (t) {
const expect2 = Buffer.from([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA])
res.on('searchEntry', function (entry) {
t.ok(entry)
t.ok(entry instanceof ldap.SearchEntry)
t.ok(entry instanceof SearchResultEntry)
t.equal(entry.dn.toString(), 'cn=bin,' + SUFFIX)
t.ok(entry.attributes)
t.ok(entry.attributes.length)
t.equal(entry.attributes[0].type, 'foo;binary')
t.equal(entry.attributes[0].vals[0], expect.toString('base64'))
t.equal(entry.attributes[0].values[0], expect.toString('base64'))
t.equal(entry.attributes[0].buffers[0].toString('base64'),
expect.toString('base64'))
@ -1244,7 +1266,6 @@ tap.test('GH-21 binary attributes', function (t) {
t.equal(expect2.length, entry.attributes[1].buffers[0].length)
for (let i = 0; i < expect2.length; i++) { t.equal(expect2[i], entry.attributes[1].buffers[0][i]) }
t.ok(entry.object)
gotEntry++
})
res.on('error', function (err) {
@ -1252,7 +1273,7 @@ tap.test('GH-21 binary attributes', function (t) {
})
res.on('end', function (res) {
t.ok(res)
t.ok(res instanceof ldap.SearchResponse)
t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0)
t.equal(gotEntry, 1)
t.end()
@ -1271,12 +1292,11 @@ tap.test('GH-23 case insensitive attribute filtering', function (t) {
let gotEntry = 0
res.on('searchEntry', function (entry) {
t.ok(entry)
t.ok(entry instanceof ldap.SearchEntry)
t.ok(entry instanceof SearchResultEntry)
t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
t.ok(entry.attributes)
t.ok(entry.attributes.length)
t.equal(entry.attributes[0].type, 'cn')
t.ok(entry.object)
gotEntry++
})
res.on('error', function (err) {
@ -1284,7 +1304,7 @@ tap.test('GH-23 case insensitive attribute filtering', function (t) {
})
res.on('end', function (res) {
t.ok(res)
t.ok(res instanceof ldap.SearchResponse)
t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0)
t.equal(gotEntry, 2)
t.end()
@ -1303,13 +1323,12 @@ tap.test('GH-24 attribute selection of *', function (t) {
let gotEntry = 0
res.on('searchEntry', function (entry) {
t.ok(entry)
t.ok(entry instanceof ldap.SearchEntry)
t.ok(entry instanceof SearchResultEntry)
t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
t.ok(entry.attributes)
t.ok(entry.attributes.length)
t.equal(entry.attributes[0].type, 'cn')
t.equal(entry.attributes[1].type, 'SN')
t.ok(entry.object)
gotEntry++
})
res.on('error', function (err) {
@ -1317,7 +1336,7 @@ tap.test('GH-24 attribute selection of *', function (t) {
})
res.on('end', function (res) {
t.ok(res)
t.ok(res instanceof ldap.SearchResponse)
t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0)
t.equal(gotEntry, 2)
t.end()
@ -1654,7 +1673,7 @@ tap.test('connection timeout', function (t) {
})
})
tap.only('emitError', function (t) {
tap.test('emitError', function (t) {
t.test('connectTimeout', function (t) {
getPort().then(function (unusedPortNumber) {
const client = ldap.createClient({

View File

@ -1,7 +1,7 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('asn1')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { Control, getControl } = require('../../lib')
test('new no args', function (t) {

View File

@ -1,66 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('asn1')
const { getControl, EntryChangeNotificationControl } = require('../../lib')
test('new no args', function (t) {
t.ok(new EntryChangeNotificationControl())
t.end()
})
test('new with args', function (t) {
const c = new EntryChangeNotificationControl({
type: '2.16.840.1.113730.3.4.7',
criticality: true,
value: {
changeType: 8,
previousDN: 'cn=foobarbazcar',
changeNumber: 123456789
}
})
t.ok(c)
t.equal(c.type, '2.16.840.1.113730.3.4.7')
t.ok(c.criticality)
t.equal(c.value.changeType, 8)
t.equal(c.value.previousDN, 'cn=foobarbazcar')
t.equal(c.value.changeNumber, 123456789)
const writer = new BerWriter()
c.toBer(writer)
const reader = new BerReader(writer.buffer)
const psc = getControl(reader)
t.ok(psc)
t.equal(psc.type, '2.16.840.1.113730.3.4.7')
t.ok(psc.criticality)
t.equal(psc.value.changeType, 8)
t.equal(psc.value.previousDN, 'cn=foobarbazcar')
t.equal(psc.value.changeNumber, 123456789)
t.end()
})
test('tober', function (t) {
const psc = new EntryChangeNotificationControl({
type: '2.16.840.1.113730.3.4.7',
criticality: true,
value: {
changeType: 8,
previousDN: 'cn=foobarbazcar',
changeNumber: 123456789
}
})
const ber = new BerWriter()
psc.toBer(ber)
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, '2.16.840.1.113730.3.4.7')
t.ok(c.criticality)
t.equal(c.value.changeType, 8)
t.equal(c.value.previousDN, 'cn=foobarbazcar')
t.equal(c.value.changeNumber, 123456789)
t.end()
})

View File

@ -1,61 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('asn1')
const { getControl, PagedResultsControl } = require('../../lib')
test('new no args', function (t) {
t.ok(new PagedResultsControl())
t.end()
})
test('new with args', function (t) {
const c = new PagedResultsControl({
type: '1.2.840.113556.1.4.319',
criticality: true,
value: {
size: 1000,
cookie: Buffer.from([1, 2, 3])
}
})
t.ok(c)
t.equal(c.type, '1.2.840.113556.1.4.319')
t.ok(c.criticality)
t.equal(c.value.size, 1000)
t.equal(Buffer.compare(c.value.cookie, Buffer.from([1, 2, 3])), 0)
const writer = new BerWriter()
c.toBer(writer)
const reader = new BerReader(writer.buffer)
const psc = getControl(reader)
t.ok(psc)
t.equal(psc.type, '1.2.840.113556.1.4.319')
t.ok(psc.criticality)
t.equal(psc.value.size, 1000)
t.equal(Buffer.compare(psc.value.cookie, Buffer.from([1, 2, 3])), 0)
t.end()
})
test('tober', function (t) {
const psc = new PagedResultsControl({
type: '1.2.840.113556.1.4.319',
criticality: true,
value: {
size: 20,
cookie: Buffer.alloc(0)
}
})
const ber = new BerWriter()
psc.toBer(ber)
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, '1.2.840.113556.1.4.319')
t.ok(c.criticality)
t.equal(c.value.size, 20)
t.equal(Buffer.compare(c.value.cookie, Buffer.alloc(0)), 0)
t.end()
})

View File

@ -1,99 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const test = require('tap').test
const asn1 = require('asn1')
const BerReader = asn1.BerReader
const BerWriter = asn1.BerWriter
let getControl
let PersistentSearchControl
/// --- Tests
test('load library', function (t) {
PersistentSearchControl = require('../../lib').PersistentSearchControl
t.ok(PersistentSearchControl)
getControl = require('../../lib').getControl
t.ok(getControl)
t.end()
})
test('new no args', function (t) {
t.ok(new PersistentSearchControl())
t.end()
})
test('new with args', function (t) {
const c = new PersistentSearchControl({
type: '2.16.840.1.113730.3.4.3',
criticality: true,
value: {
changeTypes: 15,
changesOnly: false,
returnECs: false
}
})
t.ok(c)
t.equal(c.type, '2.16.840.1.113730.3.4.3')
t.ok(c.criticality)
t.equal(c.value.changeTypes, 15)
t.equal(c.value.changesOnly, false)
t.equal(c.value.returnECs, false)
const writer = new BerWriter()
c.toBer(writer)
const reader = new BerReader(writer.buffer)
const psc = getControl(reader)
t.ok(psc)
t.equal(psc.type, '2.16.840.1.113730.3.4.3')
t.ok(psc.criticality)
t.equal(psc.value.changeTypes, 15)
t.equal(psc.value.changesOnly, false)
t.equal(psc.value.returnECs, false)
t.end()
})
test('getControl with args', function (t) {
const buf = Buffer.from([
0x30, 0x26, 0x04, 0x17, 0x32, 0x2e, 0x31, 0x36, 0x2e, 0x38, 0x34, 0x30,
0x2e, 0x31, 0x2e, 0x31, 0x31, 0x33, 0x37, 0x33, 0x30, 0x2e, 0x33, 0x2e,
0x34, 0x2e, 0x33, 0x04, 0x0b, 0x30, 0x09, 0x02, 0x01, 0x0f, 0x01, 0x01,
0xff, 0x01, 0x01, 0xff])
const ber = new BerReader(buf)
const psc = getControl(ber)
t.ok(psc)
t.equal(psc.type, '2.16.840.1.113730.3.4.3')
t.equal(psc.criticality, false)
t.equal(psc.value.changeTypes, 15)
t.equal(psc.value.changesOnly, true)
t.equal(psc.value.returnECs, true)
t.end()
})
test('tober', function (t) {
const psc = new PersistentSearchControl({
type: '2.16.840.1.113730.3.4.3',
criticality: true,
value: {
changeTypes: 15,
changesOnly: false,
returnECs: false
}
})
const ber = new BerWriter()
psc.toBer(ber)
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, '2.16.840.1.113730.3.4.3')
t.ok(c.criticality)
t.equal(c.value.changeTypes, 15)
t.equal(c.value.changesOnly, false)
t.equal(c.value.returnECs, false)
t.end()
})

View File

@ -1,95 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('asn1')
const { getControl, ServerSideSortingRequestControl: SSSRControl } = require('../../lib')
test('new no args', function (t) {
t.ok(new SSSRControl())
t.end()
})
test('new with args', function (t) {
const c = new SSSRControl({
criticality: true,
value: {
attributeType: 'sn'
}
})
t.ok(c)
t.equal(c.type, '1.2.840.113556.1.4.473')
t.ok(c.criticality)
t.equal(c.value.length, 1)
t.equal(c.value[0].attributeType, 'sn')
t.end()
})
test('toBer - object', function (t) {
const sssc = new SSSRControl({
criticality: true,
value: {
attributeType: 'sn',
orderingRule: 'caseIgnoreOrderingMatch',
reverseOrder: true
}
})
const ber = new BerWriter()
sssc.toBer(ber)
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, '1.2.840.113556.1.4.473')
t.ok(c.criticality)
t.equal(c.value[0].attributeType, 'sn')
t.equal(c.value[0].orderingRule, 'caseIgnoreOrderingMatch')
t.equal(c.value[0].reverseOrder, true)
t.end()
})
test('toBer - array', function (t) {
const sssc = new SSSRControl({
criticality: true,
value: [
{
attributeType: 'sn',
orderingRule: 'caseIgnoreOrderingMatch',
reverseOrder: true
},
{
attributeType: 'givenName',
orderingRule: 'caseIgnoreOrderingMatch'
}
]
})
const ber = new BerWriter()
sssc.toBer(ber)
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, '1.2.840.113556.1.4.473')
t.ok(c.criticality)
t.equal(c.value.length, 2)
t.equal(c.value[0].attributeType, 'sn')
t.equal(c.value[0].orderingRule, 'caseIgnoreOrderingMatch')
t.equal(c.value[0].reverseOrder, true)
t.equal(c.value[1].attributeType, 'givenName')
t.equal(c.value[1].orderingRule, 'caseIgnoreOrderingMatch')
t.end()
})
test('toBer - empty', function (t) {
const sssc = new SSSRControl()
const ber = new BerWriter()
sssc.toBer(ber)
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, '1.2.840.113556.1.4.473')
t.equal(c.value.length, 0)
t.end()
})

View File

@ -1,105 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('asn1')
const ldap = require('../../lib')
const { getControl, ServerSideSortingResponseControl: SSSResponseControl } = ldap
const OID = '1.2.840.113556.1.4.474'
test('new no args', function (t) {
const c = new SSSResponseControl()
t.ok(c)
t.equal(c.type, OID)
t.equal(c.criticality, false)
t.end()
})
test('new with args', function (t) {
const c = new SSSResponseControl({
criticality: true,
value: {
result: ldap.LDAP_SUCCESS,
failedAttribute: 'cn'
}
})
t.ok(c)
t.equal(c.type, OID)
t.equal(c.criticality, false)
t.equal(c.value.result, ldap.LDAP_SUCCESS)
t.equal(c.value.failedAttribute, 'cn')
t.end()
})
test('toBer - success', function (t) {
const sssc = new SSSResponseControl({
value: {
result: ldap.LDAP_SUCCESS,
failedAttribute: 'foobar'
}
})
const ber = new BerWriter()
sssc.toBer(ber)
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, '1.2.840.113556.1.4.474')
t.equal(c.criticality, false)
t.equal(c.value.result, ldap.LDAP_SUCCESS)
t.notOk(c.value.failedAttribute)
t.end()
})
test('toBer - simple failure', function (t) {
const sssc = new SSSResponseControl({
value: {
result: ldap.LDAP_NO_SUCH_ATTRIBUTE
}
})
const ber = new BerWriter()
sssc.toBer(ber)
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, OID)
t.equal(c.criticality, false)
t.equal(c.value.result, ldap.LDAP_NO_SUCH_ATTRIBUTE)
t.notOk(c.value.failedAttribute)
t.end()
})
test('toBer - detailed failure', function (t) {
const sssc = new SSSResponseControl({
value: {
result: ldap.LDAP_NO_SUCH_ATTRIBUTE,
failedAttribute: 'foobar'
}
})
const ber = new BerWriter()
sssc.toBer(ber)
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, OID)
t.equal(c.criticality, false)
t.equal(c.value.result, ldap.LDAP_NO_SUCH_ATTRIBUTE)
t.equal(c.value.failedAttribute, 'foobar')
t.end()
})
test('toBer - empty', function (t) {
const sssc = new SSSResponseControl()
const ber = new BerWriter()
sssc.toBer(ber)
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, OID)
t.equal(c.criticality, false)
t.notOk(c.value.result)
t.notOk(c.value.failedAttribute)
t.end()
})

View File

@ -1,94 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('asn1')
const { getControl, VirtualListViewRequestControl: VLVRControl } = require('../../lib')
test('VLV request - new no args', function (t) {
t.ok(new VLVRControl())
t.end()
})
test('VLV request - new with args', function (t) {
const c = new VLVRControl({
criticality: true,
value: {
beforeCount: 0,
afterCount: 3,
targetOffset: 1,
contentCount: 0
}
})
t.ok(c)
t.equal(c.type, '2.16.840.1.113730.3.4.9')
t.ok(c.criticality)
t.equal(c.value.beforeCount, 0)
t.equal(c.value.afterCount, 3)
t.equal(c.value.targetOffset, 1)
t.equal(c.value.contentCount, 0)
t.end()
})
test('VLV request - toBer - with offset', function (t) {
const vlvc = new VLVRControl({
criticality: true,
value: {
beforeCount: 0,
afterCount: 3,
targetOffset: 1,
contentCount: 0
}
})
const ber = new BerWriter()
vlvc.toBer(ber)
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, '2.16.840.1.113730.3.4.9')
t.ok(c.criticality)
t.equal(c.value.beforeCount, 0)
t.equal(c.value.afterCount, 3)
t.equal(c.value.targetOffset, 1)
t.equal(c.value.contentCount, 0)
t.end()
})
test('VLV request - toBer - with assertion', function (t) {
const vlvc = new VLVRControl({
criticality: true,
value: {
beforeCount: 0,
afterCount: 3,
greaterThanOrEqual: '*foo*'
}
})
const ber = new BerWriter()
vlvc.toBer(ber)
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, '2.16.840.1.113730.3.4.9')
t.ok(c.criticality)
t.equal(c.value.beforeCount, 0)
t.equal(c.value.afterCount, 3)
t.equal(c.value.greaterThanOrEqual, '*foo*')
t.end()
})
test('VLV request - toBer - empty', function (t) {
const vlvc = new VLVRControl()
const ber = new BerWriter()
vlvc.toBer(ber)
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, '2.16.840.1.113730.3.4.9')
t.equal(c.criticality, false)
t.notOk(c.value.result)
t.end()
})

View File

@ -1,68 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('asn1')
const ldap = require('../../lib')
const { getControl, VirtualListViewResponseControl: VLVResponseControl } = require('../../lib')
const OID = '2.16.840.1.113730.3.4.10'
test('VLV response - new no args', function (t) {
const c = new VLVResponseControl()
t.ok(c)
t.equal(c.type, OID)
t.equal(c.criticality, false)
t.end()
})
test('VLV response - new with args', function (t) {
const c = new VLVResponseControl({
criticality: true,
value: {
result: ldap.LDAP_SUCCESS,
targetPosition: 0,
contentCount: 10
}
})
t.ok(c)
t.equal(c.type, OID)
t.equal(c.criticality, false)
t.equal(c.value.result, ldap.LDAP_SUCCESS)
t.equal(c.value.targetPosition, 0)
t.equal(c.value.contentCount, 10)
t.end()
})
test('VLV response - toBer', function (t) {
const vlpc = new VLVResponseControl({
value: {
targetPosition: 0,
contentCount: 10,
result: ldap.LDAP_SUCCESS
}
})
const ber = new BerWriter()
vlpc.toBer(ber)
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, OID)
t.equal(c.criticality, false)
t.equal(c.value.result, ldap.LDAP_SUCCESS)
t.equal(c.value.targetPosition, 0)
t.equal(c.value.contentCount, 10)
t.end()
})
test('VLV response - toBer - empty', function (t) {
const vlpc = new VLVResponseControl()
const ber = new BerWriter()
vlpc.toBer(ber)
const c = getControl(new BerReader(ber.buffer))
t.ok(c)
t.equal(c.type, OID)
t.equal(c.criticality, false)
t.notOk(c.value.result)
t.end()
})

View File

@ -1,234 +0,0 @@
'use strict'
const { test } = require('tap')
const { dn } = require('../lib')
test('parse basic', function (t) {
const DN_STR = 'cn=mark, ou=people, o=joyent'
const name = dn.parse(DN_STR)
t.ok(name)
t.ok(name.rdns)
t.ok(Array.isArray(name.rdns))
t.equal(3, name.rdns.length)
name.rdns.forEach(function (rdn) {
t.equal('object', typeof (rdn))
})
t.equal(name.toString(), DN_STR)
t.end()
})
test('parse escaped', function (t) {
const DN_STR = 'cn=m\\,ark, ou=people, o=joyent'
const name = dn.parse(DN_STR)
t.ok(name)
t.ok(name.rdns)
t.ok(Array.isArray(name.rdns))
t.equal(3, name.rdns.length)
name.rdns.forEach(function (rdn) {
t.equal('object', typeof (rdn))
})
t.equal(name.toString(), DN_STR)
t.end()
})
test('parse compound', function (t) {
const DN_STR = 'cn=mark+sn=cavage, ou=people, o=joyent'
const name = dn.parse(DN_STR)
t.ok(name)
t.ok(name.rdns)
t.ok(Array.isArray(name.rdns))
t.equal(3, name.rdns.length)
name.rdns.forEach(function (rdn) {
t.equal('object', typeof (rdn))
})
t.equal(name.toString(), DN_STR)
t.end()
})
test('parse quoted', function (t) {
const DN_STR = 'cn="mark+sn=cavage", ou=people, o=joyent'
const ESCAPE_STR = 'cn=mark\\+sn\\=cavage, ou=people, o=joyent'
const name = dn.parse(DN_STR)
t.ok(name)
t.ok(name.rdns)
t.ok(Array.isArray(name.rdns))
t.equal(3, name.rdns.length)
name.rdns.forEach(function (rdn) {
t.equal('object', typeof (rdn))
})
t.equal(name.toString(), ESCAPE_STR)
t.end()
})
test('equals', function (t) {
const dn1 = dn.parse('cn=foo,dc=bar')
t.ok(dn1.equals('cn=foo,dc=bar'))
t.ok(!dn1.equals('cn=foo1,dc=bar'))
t.ok(dn1.equals(dn.parse('cn=foo,dc=bar')))
t.ok(!dn1.equals(dn.parse('cn=foo2,dc=bar')))
t.end()
})
test('child of', function (t) {
const dn1 = dn.parse('cn=foo,dc=bar')
t.ok(dn1.childOf('dc=bar'))
t.ok(!dn1.childOf('dc=moo'))
t.ok(!dn1.childOf('dc=foo'))
t.ok(!dn1.childOf('cn=foo,dc=bar'))
t.ok(dn1.childOf(dn.parse('dc=bar')))
t.end()
})
test('parent of', function (t) {
const dn1 = dn.parse('cn=foo,dc=bar')
t.ok(dn1.parentOf('cn=moo,cn=foo,dc=bar'))
t.ok(!dn1.parentOf('cn=moo,cn=bar,dc=foo'))
t.ok(!dn1.parentOf('cn=foo,dc=bar'))
t.ok(dn1.parentOf(dn.parse('cn=moo,cn=foo,dc=bar')))
t.end()
})
test('DN parent', function (t) {
const _dn = dn.parse('cn=foo,ou=bar')
const parent1 = _dn.parent()
const parent2 = parent1.parent()
t.ok(parent1.equals('ou=bar'))
t.ok(parent2.equals(''))
t.equal(parent2.parent(), null)
t.end()
})
test('empty DNs', function (t) {
const _dn = dn.parse('')
const _dn2 = dn.parse('cn=foo')
t.ok(_dn.isEmpty())
t.notOk(_dn2.isEmpty())
t.notOk(_dn.equals('cn=foo'))
t.notOk(_dn2.equals(''))
t.ok(_dn.parentOf('cn=foo'))
t.notOk(_dn.childOf('cn=foo'))
t.notOk(_dn2.parentOf(''))
t.ok(_dn2.childOf(''))
t.end()
})
test('case insensitive attribute names', function (t) {
const dn1 = dn.parse('CN=foo,dc=bar')
t.ok(dn1.equals('cn=foo,dc=bar'))
t.ok(dn1.equals(dn.parse('cn=foo,DC=bar')))
t.end()
})
test('format', function (t) {
const DN_ORDER = dn.parse('sn=bar+cn=foo,ou=test')
const DN_QUOTE = dn.parse('cn="foo",ou=test')
const DN_QUOTE2 = dn.parse('cn=" foo",ou=test')
const DN_SPACE = dn.parse('cn=foo,ou=test')
const DN_SPACE2 = dn.parse('cn=foo ,ou=test')
const DN_CASE = dn.parse('CN=foo,Ou=test')
t.equal(DN_ORDER.format({ keepOrder: false }), 'cn=foo+sn=bar, ou=test')
t.equal(DN_ORDER.format({ keepOrder: true }), 'sn=bar+cn=foo, ou=test')
t.equal(DN_QUOTE.format({ keepQuote: false }), 'cn=foo, ou=test')
t.equal(DN_QUOTE.format({ keepQuote: true }), 'cn="foo", ou=test')
t.equal(DN_QUOTE2.format({ keepQuote: false }), 'cn=" foo", ou=test')
t.equal(DN_QUOTE2.format({ keepQuote: true }), 'cn=" foo", ou=test')
t.equal(DN_SPACE.format({ keepSpace: false }), 'cn=foo, ou=test')
t.equal(DN_SPACE.format({ keepSpace: true }), 'cn=foo,ou=test')
t.equal(DN_SPACE.format({ skipSpace: true }), 'cn=foo,ou=test')
t.equal(DN_SPACE2.format({ keepSpace: false }), 'cn=foo, ou=test')
t.equal(DN_SPACE2.format({ keepSpace: true }), 'cn=foo ,ou=test')
t.equal(DN_SPACE2.format({ skipSpace: true }), 'cn=foo,ou=test')
t.equal(DN_CASE.format({ keepCase: false }), 'cn=foo, ou=test')
t.equal(DN_CASE.format({ keepCase: true }), 'CN=foo, Ou=test')
t.equal(DN_CASE.format({ upperName: true }), 'CN=foo, OU=test')
t.end()
})
test('set format', function (t) {
const _dn = dn.parse('uid="user", sn=bar+cn=foo, dc=test , DC=com')
t.equal(_dn.toString(), 'uid=user, cn=foo+sn=bar, dc=test, dc=com')
_dn.setFormat({ keepOrder: true })
t.equal(_dn.toString(), 'uid=user, sn=bar+cn=foo, dc=test, dc=com')
_dn.setFormat({ keepQuote: true })
t.equal(_dn.toString(), 'uid="user", cn=foo+sn=bar, dc=test, dc=com')
_dn.setFormat({ keepSpace: true })
t.equal(_dn.toString(), 'uid=user, cn=foo+sn=bar, dc=test , dc=com')
_dn.setFormat({ keepCase: true })
t.equal(_dn.toString(), 'uid=user, cn=foo+sn=bar, dc=test, DC=com')
_dn.setFormat({ upperName: true })
t.equal(_dn.toString(), 'UID=user, CN=foo+SN=bar, DC=test, DC=com')
t.end()
})
test('format persists across clone', function (t) {
const _dn = dn.parse('uid="user", sn=bar+cn=foo, dc=test , DC=com')
const OUT = 'UID="user", CN=foo+SN=bar, DC=test, DC=com'
_dn.setFormat({ keepQuote: true, upperName: true })
const clone = _dn.clone()
t.equal(_dn.toString(), OUT)
t.equal(clone.toString(), OUT)
t.end()
})
test('initialization', function (t) {
const dn1 = new dn.DN()
t.ok(dn1)
t.equal(dn1.toString(), '')
t.ok(dn1.isEmpty(), 'DN with no initializer defaults to null DN')
const data = [
new dn.RDN({ foo: 'bar' }),
new dn.RDN({ o: 'base' })
]
const dn2 = new dn.DN(data)
t.ok(dn2)
t.equal(dn2.toString(), 'foo=bar, o=base')
t.ok(!dn2.isEmpty())
t.end()
})
test('array functions', function (t) {
const dn1 = dn.parse('a=foo, b=bar, c=baz')
t.ok(dn1)
t.equal(dn1.toString(), 'a=foo, b=bar, c=baz')
t.ok(dn1.reverse())
t.equal(dn1.toString(), 'c=baz, b=bar, a=foo')
let rdn = dn1.pop()
t.ok(rdn)
t.equal(dn1.toString(), 'c=baz, b=bar')
t.ok(dn1.push(rdn))
t.equal(dn1.toString(), 'c=baz, b=bar, a=foo')
rdn = dn1.shift()
t.ok(rdn)
t.equal(dn1.toString(), 'b=bar, a=foo')
t.ok(dn1.unshift(rdn))
t.equal(dn1.toString(), 'c=baz, b=bar, a=foo')
t.end()
})
test('isDN duck-testing', function (t) {
const valid = dn.parse('cn=foo')
const isDN = dn.DN.isDN
t.notOk(isDN(null))
t.notOk(isDN('cn=foo'))
t.ok(isDN(valid))
const duck = {
rdns: [{ look: 'ma' }, { a: 'dn' }],
toString: function () { return 'look=ma, a=dn' }
}
t.ok(isDN(duck))
t.end()
})

View File

@ -1,54 +0,0 @@
'use strict'
const { test } = require('tap')
const { filters: { EqualityFilter, AndFilter } } = require('../../lib')
test('Construct no args', function (t) {
t.ok(new AndFilter())
t.end()
})
test('Construct args', function (t) {
const f = new AndFilter()
f.addFilter(new EqualityFilter({
attribute: 'foo',
value: 'bar'
}))
f.addFilter(new EqualityFilter({
attribute: 'zig',
value: 'zag'
}))
t.ok(f)
t.equal(f.toString(), '(&(foo=bar)(zig=zag))')
t.end()
})
test('match true', function (t) {
const f = new AndFilter()
f.addFilter(new EqualityFilter({
attribute: 'foo',
value: 'bar'
}))
f.addFilter(new EqualityFilter({
attribute: 'zig',
value: 'zag'
}))
t.ok(f)
t.ok(f.matches({ foo: 'bar', zig: 'zag' }))
t.end()
})
test('match false', function (t) {
const f = new AndFilter()
f.addFilter(new EqualityFilter({
attribute: 'foo',
value: 'bar'
}))
f.addFilter(new EqualityFilter({
attribute: 'zig',
value: 'zag'
}))
t.ok(f)
t.ok(!f.matches({ foo: 'bar', zig: 'zonk' }))
t.end()
})

Some files were not shown because too many files have changed in this diff Show More