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, es2021: true,
node: true node: true
}, },
parserOptions: {
ecmaVersion: 'latest'
},
extends: [ extends: [
'standard' 'standard'
], ],

View File

@ -10,10 +10,10 @@ jobs:
name: Update Docs name: Update Docs
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-node@v2.5.1 - uses: actions/setup-node@v3
with: with:
node-version: '14' node-version: '18'
- name: Install Packages - name: Install Packages
run: npm install run: npm install
- name: Build Docs - 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 # https://github.community/t5/GitHub-Actions/Github-Actions-services-not-reachable/m-p/30739/highlight/true#M538
on: on:
push:
branches:
- master
- next
pull_request: pull_request:
branches: branches:
- master - master
- next
jobs: jobs:
baseline: baseline:
name: Baseline Tests name: Baseline Tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
# services: services:
# openldap: openldap:
# image: docker.pkg.github.com/ldapjs/docker-test-openldap/openldap:1.0 image: ghcr.io/ldapjs/docker-test-openldap/openldap:2023-10-30
# ports: ports:
# - 389:389 - 389:389
# - 636:636 - 636:636
options: >
--health-cmd "ldapsearch -Y EXTERNAL -Q -H ldapi:// -b ou=people,dc=planetexpress,dc=com -LLL '(cn=Turanga Leela)' cn"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-node@v2.5.1 - uses: actions/setup-node@v3
with:
# Hack way to start service since GitHub doesn't integrate with its own services node-version: 'lts/*'
- 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
- name: Install Packages - name: Install Packages
run: npm install run: npm install

View File

@ -4,17 +4,21 @@ on:
push: push:
branches: branches:
- master - master
- next
pull_request: pull_request:
branches: branches:
- master - master
- next
jobs: jobs:
lint: lint:
name: Lint Check name: Lint Check
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-node@v2.5.1 - uses: actions/setup-node@v3
with:
node-version: 'lts/*'
- name: Install Packages - name: Install Packages
run: npm install run: npm install
- name: Lint Code - name: Lint Code
@ -28,27 +32,16 @@ jobs:
- ubuntu-latest - ubuntu-latest
- windows-latest - windows-latest
node: node:
- 10.13.0 - 16
- 10.x - 18
- 12.x - 20
- 14.x
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-node@v2.5.1 - uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node }} node-version: ${{ matrix.node }}
- name: Install Packages - name: Install Packages
run: npm install run: npm install
- name: Run Tests - name: Run Tests
run: npm run test:ci 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) This project has been decomissioned. I, James Sumners, took it on when it was
[![Coverage Status](https://coveralls.io/repos/github/ldapjs/node-ldapjs/badge.svg)](https://coveralls.io/github/ldapjs/node-ldapjs/) 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 My recommendation to you in regard to LDAP operations: write a gateway in a
var ldap = require('ldapjs'); 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) { P.S.: if I ever do need this project again, I might revive it. But I'd fight
var obj = { hard for my suggestion above. Also, I will consider turning it over to an
dn: req.dn.toString(), interested party, but I will require at least one recommendation from a
attributes: { Node.js core contributor that I can vet with the people that I know on that
objectclass: ['organization', 'top'], team.
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>.

View File

@ -1,8 +1,10 @@
version: '3'
services: services:
openldap: openldap:
image: docker.pkg.github.com/ldapjs/docker-test-openldap/openldap:latest image: ghcr.io/ldapjs/docker-test-openldap/openldap:2023-10-30
ports: ports:
- 389:389 - 389:389
- 636:636 - 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'] url: ['ldap://127.0.0.1:1389', 'ldap://127.0.0.2:1389']
}); });
client.on('error', (err) => { client.on('connectError', (err) => {
// handle connection error // handle connection error
}) })
``` ```
@ -41,7 +41,6 @@ client is:
|connectTimeout |Milliseconds client should wait before timing out on TCP connections (Default: OS default)| |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)| |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| |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)| |reconnect |Try to reconnect when the connection gets lost (Default is false)|
### url ### url
@ -214,7 +213,8 @@ Example:
const change = new ldap.Change({ const change = new ldap.Change({
operation: 'add', operation: 'add',
modification: { 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. | | 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`. | | 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
`modifyDN(dn, newDN, controls, callback)` `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` each `searchEntry` that comes back from the server. You will additionally be able to listen for a `searchRequest`
, `searchReference`, `error` and `end` event. , `searchReference`, `error` and `end` event.
`searchRequest` is emitted immediately after every `SearchRequest` is sent with a `SearchRequest` parameter. You can do operations `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 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 (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 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); assert.ifError(err);
res.on('searchRequest', (searchRequest) => { res.on('searchRequest', (searchRequest) => {
console.log('searchRequest: ', searchRequest.messageID); console.log('searchRequest: ', searchRequest.messageId);
}); });
res.on('searchEntry', (entry) => { res.on('searchEntry', (entry) => {
console.log('entry: ' + JSON.stringify(entry.object)); console.log('entry: ' + JSON.stringify(entry.pojo));
}); });
res.on('searchReference', (referral) => { res.on('searchReference', (referral) => {
console.log('referral: ' + referral.uris.join()); console.log('referral: ' + referral.uris.join());

View File

@ -80,8 +80,15 @@ Example:
`listen(port, [host], [callback])` `listen(port, [host], [callback])`
Begin accepting connections on the specified port and host. If the host is Begin accepting connections on the specified port and host. If the host is
omitted, the server will accept connections directed to any IPv4 address omitted, the server will accept connections directed to the IPv4 address
(INADDR\_ANY). `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 This function is asynchronous. The last parameter callback will be called when
the server has been bound. 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`. will be there defaulted to `cn=anonymous`.
Additionally, request will have a `logId` parameter you can use to uniquely 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 ## 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': case 'base':
if (req.filter.matches(db[dn])) { if (req.filter.matches(db[dn])) {
res.send({ res.send({
dn: dn, dn,
attributes: db[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 assert = require('assert-plus')
const VError = require('verror').VError const VError = require('verror').VError
const Attribute = require('../attribute') const Attribute = require('@ldapjs/attribute')
const Change = require('../change') const Change = require('@ldapjs/change')
const Control = require('../controls/index').Control const Control = require('../controls/index').Control
const { Control: LdapControl } = require('@ldapjs/controls')
const SearchPager = require('./search_pager') const SearchPager = require('./search_pager')
const Protocol = require('../protocol') const Protocol = require('@ldapjs/protocol')
const dn = require('../dn') const { DN } = require('@ldapjs/dn')
const errors = require('../errors') const errors = require('../errors')
const filters = require('../filters') const filters = require('@ldapjs/filter')
const messages = require('../messages') const Parser = require('../messages/parser')
const url = require('../url') const url = require('../url')
const CorkedEmitter = require('../corked_emitter') const CorkedEmitter = require('../corked_emitter')
/// --- Globals /// --- Globals
const AbandonRequest = messages.AbandonRequest const messages = require('@ldapjs/messages')
const AddRequest = messages.AddRequest const {
const BindRequest = messages.BindRequest AbandonRequest,
const CompareRequest = messages.CompareRequest AddRequest,
const DeleteRequest = messages.DeleteRequest BindRequest,
const ExtendedRequest = messages.ExtendedRequest CompareRequest,
const ModifyRequest = messages.ModifyRequest DeleteRequest,
const ModifyDNRequest = messages.ModifyDNRequest ExtensionRequest: ExtendedRequest,
const SearchRequest = messages.SearchRequest ModifyRequest,
const UnbindRequest = messages.UnbindRequest ModifyDnRequest: ModifyDNRequest,
const UnbindResponse = messages.UnbindResponse SearchRequest,
UnbindRequest,
const LDAPResult = messages.LDAPResult LdapResult: LDAPResult,
const SearchEntry = messages.SearchEntry SearchResultEntry: SearchEntry,
const SearchReference = messages.SearchReference SearchResultReference: SearchReference
// var SearchResponse = messages.SearchResponse } = messages
const Parser = messages.Parser
const PresenceFilter = filters.PresenceFilter const PresenceFilter = filters.PresenceFilter
@ -67,9 +67,9 @@ function nextClientId () {
function validateControls (controls) { function validateControls (controls) {
if (Array.isArray(controls)) { if (Array.isArray(controls)) {
controls.forEach(function (c) { 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] controls = [controls]
} else { } else {
throw new TypeError('controls must be [Control]') throw new TypeError('controls must be [Control]')
@ -78,13 +78,11 @@ function validateControls (controls) {
return controls return controls
} }
function ensureDN (input, strict) { function ensureDN (input) {
if (dn.DN.isDN(input)) { if (DN.isDn(input)) {
return dn
} else if (strict) {
return dn.parse(input)
} else if (typeof (input) === 'string') {
return input return input
} else if (typeof (input) === 'string') {
return DN.fromString(input)
} else { } else {
throw new Error('invalid DN') throw new Error('invalid DN')
} }
@ -136,7 +134,6 @@ function Client (options) {
failAfter: parseInt(rOpts.failAfter, 10) || Infinity failAfter: parseInt(rOpts.failAfter, 10) || Infinity
} }
} }
this.strictDN = (options.strictDN !== undefined) ? options.strictDN : true
this.queue = requestQueueFactory({ this.queue = requestQueueFactory({
size: parseInt((options.queueSize || 0), 10), 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 * The callback will be invoked as soon as the data is flushed out to the
* network, as there is never a response from abandon. * 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 {Control} controls (optional) either a Control or [Control].
* @param {Function} callback of the form f(err). * @param {Function} callback of the form f(err).
* @throws {TypeError} on invalid input. * @throws {TypeError} on invalid input.
*/ */
Client.prototype.abandon = function abandon (messageID, controls, callback) { Client.prototype.abandon = function abandon (messageId, controls, callback) {
assert.number(messageID, 'messageID') assert.number(messageId, 'messageId')
if (typeof (controls) === 'function') { if (typeof (controls) === 'function') {
callback = controls callback = controls
controls = [] controls = []
@ -193,8 +190,8 @@ Client.prototype.abandon = function abandon (messageID, controls, callback) {
assert.func(callback, 'callback') assert.func(callback, 'callback')
const req = new AbandonRequest({ const req = new AbandonRequest({
abandonID: messageID, abandonId: messageId,
controls: controls controls
}) })
return this._send(req, 'abandon', null, callback) return this._send(req, 'abandon', null, callback)
@ -248,9 +245,9 @@ Client.prototype.add = function add (name, entry, controls, callback) {
} }
const req = new AddRequest({ const req = new AddRequest({
entry: ensureDN(name, this.strictDN), entry: ensureDN(name),
attributes: entry, attributes: entry,
controls: controls controls
}) })
return this._send(req, [errors.LDAP_SUCCESS], null, callback) return this._send(req, [errors.LDAP_SUCCESS], null, callback)
@ -270,7 +267,12 @@ Client.prototype.bind = function bind (name,
controls, controls,
callback, callback,
_bypass) { _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') assert.optionalString(credentials, 'credentials')
if (typeof (controls) === 'function') { if (typeof (controls) === 'function') {
callback = controls callback = controls
@ -284,7 +286,7 @@ Client.prototype.bind = function bind (name,
name: name || '', name: name || '',
authentication: 'Simple', authentication: 'Simple',
credentials: credentials || '', credentials: credentials || '',
controls: controls controls
}) })
// Connection errors will be reported to the bind callback too (useful when the LDAP server is not available) // 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') assert.func(callback, 'callback')
const req = new CompareRequest({ const req = new CompareRequest({
entry: ensureDN(name, this.strictDN), entry: ensureDN(name),
attribute: attr, attribute: attr,
value: value, value,
controls: controls controls
}) })
return this._send(req, CMP_EXPECT, null, function (err, res) { 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') assert.func(callback, 'callback')
const req = new DeleteRequest({ const req = new DeleteRequest({
entry: ensureDN(name, this.strictDN), entry: ensureDN(name),
controls: controls controls
}) })
return this._send(req, [errors.LDAP_SUCCESS], null, callback) 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({ const req = new ExtendedRequest({
requestName: name, requestName: name,
requestValue: value, requestValue: value,
controls: controls controls
}) })
return this._send(req, [errors.LDAP_SUCCESS], null, function (err, res) { 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') assert.func(callback, 'callback')
const req = new ModifyRequest({ const req = new ModifyRequest({
object: ensureDN(name, this.strictDN), object: ensureDN(name),
changes: changes, changes,
controls: controls controls
}) })
return this._send(req, [errors.LDAP_SUCCESS], null, callback) return this._send(req, [errors.LDAP_SUCCESS], null, callback)
@ -504,18 +506,16 @@ Client.prototype.modifyDN = function modifyDN (name,
} }
assert.func(callback) assert.func(callback)
const DN = ensureDN(name) const newDN = DN.fromString(newName)
// TODO: is non-strict handling desired here?
const newDN = dn.parse(newName)
const req = new ModifyDNRequest({ const req = new ModifyDNRequest({
entry: DN, entry: DN.fromString(name),
deleteOldRdn: true, deleteOldRdn: true,
controls: controls controls
}) })
if (newDN.length !== 1) { if (newDN.length !== 1) {
req.newRdn = dn.parse(newDN.rdns.shift().toString()) req.newRdn = DN.fromString(newDN.shift().toString())
req.newSuperior = newDN req.newSuperior = newDN
} else { } else {
req.newRdn = newDN req.newRdn = newDN
@ -571,7 +571,7 @@ Client.prototype.search = function search (base,
options.filter = filters.parseString(options.filter) options.filter = filters.parseString(options.filter)
} else if (!options.filter) { } else if (!options.filter) {
options.filter = new PresenceFilter({ attribute: 'objectclass' }) 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') throw new TypeError('options.filter (Filter) required')
} }
if (typeof (controls) === 'function') { if (typeof (controls) === 'function') {
@ -593,14 +593,14 @@ Client.prototype.search = function search (base,
} }
const self = this const self = this
const baseDN = ensureDN(base, this.strictDN) const baseDN = ensureDN(base)
function sendRequest (ctrls, emitter, cb) { function sendRequest (ctrls, emitter, cb) {
const req = new SearchRequest({ const req = new SearchRequest({
baseObject: baseDN, baseObject: baseDN,
scope: options.scope || 'base', scope: options.scope || 'base',
filter: options.filter, filter: options.filter,
derefAliases: options.derefAliases || Protocol.NEVER_DEREF_ALIASES, derefAliases: options.derefAliases || Protocol.search.NEVER_DEREF_ALIASES,
sizeLimit: options.sizeLimit || 0, sizeLimit: options.sizeLimit || 0,
timeLimit: options.timeLimit || 10, timeLimit: options.timeLimit || 10,
typesOnly: options.typesOnly || false, typesOnly: options.typesOnly || false,
@ -629,11 +629,11 @@ Client.prototype.search = function search (base,
} }
const pager = new SearchPager({ const pager = new SearchPager({
callback: callback, callback,
controls: controls, controls,
pageSize: size, pageSize: size,
pagePause: pageOpts.pagePause, pagePause: pageOpts.pagePause,
sendRequest: sendRequest sendRequest
}) })
pager.begin() pager.begin()
} else { } else {
@ -723,7 +723,7 @@ Client.prototype.starttls = function starttls (options,
self._tracker.parser.write(data) self._tracker.parser.write(data)
}) })
secure.on('error', function (err) { 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) self.emit('error', err)
sock.destroy() sock.destroy()
@ -744,7 +744,7 @@ Client.prototype.starttls = function starttls (options,
const req = new ExtendedRequest({ const req = new ExtendedRequest({
requestName: '1.3.6.1.4.1.1466.20037', requestName: '1.3.6.1.4.1.1466.20037',
requestValue: null, requestValue: null,
controls: controls controls
}) })
return this._send(req, return this._send(req,
@ -857,7 +857,7 @@ Client.prototype.connect = function connect () {
function initSocket (server) { function initSocket (server) {
tracker = messageTrackerFactory({ tracker = messageTrackerFactory({
id: server ? server.href : self.socketPath, 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. // This won't be set on TLS. So. Very. Annoying.
@ -876,15 +876,52 @@ Client.prototype.connect = function connect () {
}) })
// The "router" // The "router"
//
// This is invoked after the incoming BER has been parsed into a JavaScript
// object.
tracker.parser.on('message', function onMessage (message) { tracker.parser.on('message', function onMessage (message) {
message.connection = self._socket 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) { if (!callback) {
log.error({ message: message.json }, 'unsolicited message') log.error({ message: message.pojo }, 'unsolicited message')
return false 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) return callback(message)
}) })
@ -965,7 +1002,7 @@ Client.prototype.connect = function connect () {
socket.end() socket.end()
}) })
socket.on('error', function onSocketError (err) { 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) self.emit('error', err)
socket.destroy() socket.destroy()
@ -1083,8 +1120,16 @@ Client.prototype._onClose = function _onClose (closeError) {
return cb(new ConnectionError(tracker.id + ' closed')) return cb(new ConnectionError(tracker.id + ' closed'))
} else { } else {
// Unbinds will be communicated as a success since we're closed // Unbinds will be communicated as a success since we're closed
const unbind = new UnbindResponse({ messageID: msgid }) // TODO: we are faking this "UnbindResponse" object in order to make
unbind.status = 'unbind' // 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) return cb(unbind)
} }
}) })
@ -1202,21 +1247,23 @@ Client.prototype._sendSocket = function _sendSocket (message,
function messageCallback (msg) { function messageCallback (msg) {
if (timer) { clearTimeout(timer) } 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 (expect === 'abandon') { return sendResult('end', null) }
if (msg instanceof SearchEntry || msg instanceof SearchReference) { if (msg instanceof SearchEntry || msg instanceof SearchReference) {
let event = msg.constructor.name 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) return sendResult(event, msg)
} else { } else {
tracker.remove(message.messageID) tracker.remove(message.messageId)
// Potentially mark client as idle // Potentially mark client as idle
self._updateIdle() self._updateIdle()
if (msg instanceof LDAPResult) { 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('error', errors.getError(msg))
} }
return sendResult('end', msg) return sendResult('end', msg)
@ -1230,7 +1277,7 @@ Client.prototype._sendSocket = function _sendSocket (message,
function onRequestTimeout () { function onRequestTimeout () {
self.emit('timeout', message) self.emit('timeout', message)
const cb = tracker.fetch(message.messageID) const { callback: cb } = tracker.fetch(message.messageId)
if (cb) { if (cb) {
// FIXME: the timed-out request should be abandoned // FIXME: the timed-out request should be abandoned
cb(new errors.TimeoutError('request timeout (client interrupt)')) cb(new errors.TimeoutError('request timeout (client interrupt)'))
@ -1239,8 +1286,8 @@ Client.prototype._sendSocket = function _sendSocket (message,
function writeCallback () { function writeCallback () {
if (expect === 'abandon') { if (expect === 'abandon') {
// Mark the messageID specified as abandoned // Mark the messageId specified as abandoned
tracker.abandon(message.abandonID) tracker.abandon(message.abandonId)
// No need to track the abandon request itself // No need to track the abandon request itself
tracker.remove(message.id) tracker.remove(message.id)
return callback(null) return callback(null)
@ -1272,10 +1319,11 @@ Client.prototype._sendSocket = function _sendSocket (message,
timer = setTimeout(onRequestTimeout, self.timeout) timer = setTimeout(onRequestTimeout, self.timeout)
} }
log.trace('sending request %j', message.json) log.trace('sending request %j', message.pojo)
try { try {
return conn.write(message.toBer(), writeCallback) const messageBer = message.toBer()
return conn.write(messageBer.buffer, writeCallback)
} catch (e) { } catch (e) {
if (timer) { clearTimeout(timer) } if (timer) { clearTimeout(timer) }

View File

@ -4,7 +4,7 @@ const logger = require('../logger')
const Client = require('./client') const Client = require('./client')
module.exports = { module.exports = {
Client: Client, Client,
createClient: function createClient (options) { createClient: function createClient (options) {
if (isObject(options) === false) throw TypeError('options (object) required') if (isObject(options) === false) throw TypeError('options (object) required')
if (options.url && typeof options.url !== 'string' && !Array.isArray(options.url)) throw TypeError('options.url (string|array) required') if (options.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) { tracker.abandon = function abandonMessage (msgID) {
if (messages.has(msgID) === false) return false if (messages.has(msgID) === false) return false
const toAbandon = messages.get(msgID)
abandoned.set(msgID, { abandoned.set(msgID, {
age: currentID, age: currentID,
cb: messages.get(msgID) message: toAbandon.message,
cb: toAbandon.callback
}) })
return messages.delete(msgID) 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 * Retrieves the message handler for a message. Removes abandoned messages
* that have been given time to be resolved. * that have been given time to be resolved.
@ -79,10 +89,10 @@ module.exports = function messageTrackerFactory (options) {
* @method fetch * @method fetch
*/ */
tracker.fetch = function fetchMessage (msgID) { tracker.fetch = function fetchMessage (msgID) {
const messageCB = messages.get(msgID) const tracked = messages.get(msgID)
if (messageCB) { if (tracked) {
purgeAbandoned(msgID, abandoned) purgeAbandoned(msgID, abandoned)
return messageCB return tracked
} }
// We sent an abandon request but the server either wasn't able to process // 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. // to be processed normally.
const abandonedMsg = abandoned.get(msgID) const abandonedMsg = abandoned.get(msgID)
if (abandonedMsg) { if (abandonedMsg) {
return abandonedMsg.cb return { message: abandonedMsg, callback: abandonedMsg.cb }
} }
return null return null
@ -110,7 +120,7 @@ module.exports = function messageTrackerFactory (options) {
messages.forEach((val, key) => { messages.forEach((val, key) => {
purgeAbandoned(key, abandoned) purgeAbandoned(key, abandoned)
tracker.remove(key) 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. * Add a message handler to be tracked.
* *
* @param {object} message The message object to be tracked. This object will * @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. * @param {function} callback The handler for the message.
* *
* @memberof MessageTracker * @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 // 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 // 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. // refactored later, then we can possibly get rid of this side effect.
message.messageID = currentID message.messageId = currentID
messages.set(currentID, callback) messages.set(currentID, { callback, message })
} }
return tracker return tracker

View File

@ -14,7 +14,7 @@
* is not accepting any requests. * is not accepting any requests.
*/ */
module.exports = function enqueue (message, expect, emitter, cb) { 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 return false
} }

View File

@ -2,14 +2,8 @@
const EventEmitter = require('events').EventEmitter const EventEmitter = require('events').EventEmitter
const util = require('util') const util = require('util')
const assert = require('assert-plus') const assert = require('assert-plus')
const { PagedResultsControl } = require('@ldapjs/controls')
// var dn = require('../dn')
// var messages = require('../messages/index')
// var Protocol = require('../protocol')
const PagedControl = require('../controls/paged_results_control.js')
const CorkedEmitter = require('../corked_emitter.js') const CorkedEmitter = require('../corked_emitter.js')
/// --- API /// --- API
@ -47,7 +41,7 @@ function SearchPager (opts) {
this.sendRequest = opts.sendRequest this.sendRequest = opts.sendRequest
this.controls.forEach(function (control) { 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. // The point of using SearchPager is not having to do this.
// Toss an error if the pagedResultsControl is present // Toss an error if the pagedResultsControl is present
throw new Error('redundant pagedResultControl') throw new Error('redundant pagedResultControl')
@ -79,7 +73,7 @@ SearchPager.prototype._onEnd = function _onEnd (res) {
const self = this const self = this
let cookie = null let cookie = null
res.controls.forEach(function (control) { res.controls.forEach(function (control) {
if (control.type === PagedControl.OID) { if (control.type === PagedResultsControl.OID) {
cookie = control.value.cookie cookie = control.value.cookie
} }
}) })
@ -95,13 +89,13 @@ SearchPager.prototype._onEnd = function _onEnd (res) {
if (this.listeners('pageError').length > 0) { if (this.listeners('pageError').length > 0) {
this.emit('pageError', err) this.emit('pageError', err)
// If the consumer as subscribed to pageError, SearchPager is absolved // 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 // event after 'error' breaks the contract that the standard client
// provides, so it's only a possibility if 'pageError' is used instead. // provides, so it's only a possibility if 'pageError' is used instead.
this.emit('end', res) this.emit('end', res)
} else { } else {
this.emit('error', err) this.emit('error', err)
// No end event possible per explaination above. // No end event possible per explanation above.
} }
return return
} }
@ -140,10 +134,10 @@ SearchPager.prototype._onError = function _onError (err) {
*/ */
SearchPager.prototype._nextPage = function _nextPage (cookie) { SearchPager.prototype._nextPage = function _nextPage (cookie) {
const controls = this.controls.slice(0) const controls = this.controls.slice(0)
controls.push(new PagedControl({ controls.push(new PagedResultsControl({
value: { value: {
size: this.pageSize, 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. // Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert') const controls = require('@ldapjs/controls')
const Ber = require('asn1').Ber module.exports = controls
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
}

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]] = { ERRORS[CODES[code]] = {
err: err, err,
message: msg 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 logger = require('./logger')
const client = require('./client') const client = require('./client')
const Attribute = require('./attribute') const Attribute = require('@ldapjs/attribute')
const Change = require('./change') const Change = require('@ldapjs/change')
const Protocol = require('./protocol') const Protocol = require('@ldapjs/protocol')
const Server = require('./server') const Server = require('./server')
const controls = require('./controls') const controls = require('./controls')
const persistentSearch = require('./persistent_search') const persistentSearch = require('./persistent_search')
const dn = require('./dn') const dn = require('@ldapjs/dn')
const errors = require('./errors') const errors = require('./errors')
const filters = require('./filters') const filters = require('@ldapjs/filter')
const messages = require('./messages') const messages = require('./messages')
const url = require('./url') const url = require('./url')
@ -24,7 +24,7 @@ module.exports = {
Client: client.Client, Client: client.Client,
createClient: client.createClient, createClient: client.createClient,
Server: Server, Server,
createServer: function (options) { createServer: function (options) {
if (options === undefined) { options = {} } if (options === undefined) { options = {} }
@ -37,21 +37,21 @@ module.exports = {
return new Server(options) return new Server(options)
}, },
Attribute: Attribute, Attribute,
Change: Change, Change,
dn: dn, dn,
DN: dn.DN, DN: dn.DN,
RDN: dn.RDN, RDN: dn.RDN,
parseDN: dn.parse, parseDN: dn.DN.fromString,
persistentSearch: persistentSearch, persistentSearch,
PersistentSearchCache: persistentSearch.PersistentSearchCache, PersistentSearchCache: persistentSearch.PersistentSearchCache,
filters: filters, filters,
parseFilter: filters.parseString, parseFilter: filters.parseString,
url: url, url,
parseURL: url.parse 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. // Copyright 2011 Mark Cavage, Inc. All rights reserved.
const LDAPMessage = require('./message') const messages = require('@ldapjs/messages')
const LDAPResult = require('./result')
const Parser = require('./parser') 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 SearchResponse = require('./search_response')
const UnbindRequest = require('./unbind_request')
const UnbindResponse = require('./unbind_response')
/// --- API /// --- API
module.exports = { module.exports = {
LDAPMessage: LDAPMessage, LDAPMessage: messages.LdapMessage,
LDAPResult: LDAPResult, LDAPResult: messages.LdapResult,
Parser: Parser, Parser,
AbandonRequest: AbandonRequest, AbandonRequest: messages.AbandonRequest,
AbandonResponse: AbandonResponse, AbandonResponse: messages.AbandonResponse,
AddRequest: AddRequest, AddRequest: messages.AddRequest,
AddResponse: AddResponse, AddResponse: messages.AddResponse,
BindRequest: BindRequest, BindRequest: messages.BindRequest,
BindResponse: BindResponse, BindResponse: messages.BindResponse,
CompareRequest: CompareRequest, CompareRequest: messages.CompareRequest,
CompareResponse: CompareResponse, CompareResponse: messages.CompareResponse,
DeleteRequest: DeleteRequest, DeleteRequest: messages.DeleteRequest,
DeleteResponse: DeleteResponse, DeleteResponse: messages.DeleteResponse,
ExtendedRequest: ExtendedRequest, ExtendedRequest: messages.ExtensionRequest,
ExtendedResponse: ExtendedResponse, ExtendedResponse: messages.ExtensionResponse,
ModifyRequest: ModifyRequest, ModifyRequest: messages.ModifyRequest,
ModifyResponse: ModifyResponse, ModifyResponse: messages.ModifyResponse,
ModifyDNRequest: ModifyDNRequest, ModifyDNRequest: messages.ModifyDnRequest,
ModifyDNResponse: ModifyDNResponse, ModifyDNResponse: messages.ModifyDnResponse,
SearchRequest: SearchRequest, SearchRequest: messages.SearchRequest,
SearchEntry: SearchEntry, SearchEntry: messages.SearchResultEntry,
SearchReference: SearchReference, SearchReference: messages.SearchResultReference,
SearchResponse: SearchResponse, SearchResponse,
UnbindRequest: UnbindRequest, UnbindRequest: messages.UnbindRequest
UnbindResponse: UnbindResponse
} }

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 util = require('util')
const assert = require('assert-plus') const assert = require('assert-plus')
const asn1 = require('asn1') const asn1 = require('@ldapjs/asn1')
// var VError = require('verror').VError
const logger = require('../logger') const logger = require('../logger')
const AbandonRequest = require('./abandon_request') const messages = require('@ldapjs/messages')
const AddRequest = require('./add_request') const AbandonRequest = messages.AbandonRequest
const AddResponse = require('./add_response') const AddRequest = messages.AddRequest
const BindRequest = require('./bind_request') const AddResponse = messages.AddResponse
const BindResponse = require('./bind_response') const BindRequest = messages.BindRequest
const CompareRequest = require('./compare_request') const BindResponse = messages.BindResponse
const CompareResponse = require('./compare_response') const CompareRequest = messages.CompareRequest
const DeleteRequest = require('./del_request') const CompareResponse = messages.CompareResponse
const DeleteResponse = require('./del_response') const DeleteRequest = messages.DeleteRequest
const ExtendedRequest = require('./ext_request') const DeleteResponse = messages.DeleteResponse
const ExtendedResponse = require('./ext_response') const ExtendedRequest = messages.ExtensionRequest
const ModifyRequest = require('./modify_request') const ExtendedResponse = messages.ExtensionResponse
const ModifyResponse = require('./modify_response') const ModifyRequest = messages.ModifyRequest
const ModifyDNRequest = require('./moddn_request') const ModifyResponse = messages.ModifyResponse
const ModifyDNResponse = require('./moddn_response') const ModifyDNRequest = messages.ModifyDnRequest
const SearchRequest = require('./search_request') const ModifyDNResponse = messages.ModifyDnResponse
const SearchEntry = require('./search_entry') const SearchRequest = messages.SearchRequest
const SearchReference = require('./search_reference') const SearchEntry = messages.SearchResultEntry
const SearchReference = messages.SearchResultReference
const SearchResponse = require('./search_response') const SearchResponse = require('./search_response')
const UnbindRequest = require('./unbind_request') const UnbindRequest = messages.UnbindRequest
// var UnbindResponse = require('./unbind_response') const LDAPResult = messages.LdapResult
const LDAPResult = require('./result') const Protocol = require('@ldapjs/protocol')
// var Message = require('./message')
const Protocol = require('../protocol')
/// --- Globals /// --- Globals
// var Ber = asn1.Ber
const BerReader = asn1.BerReader const BerReader = asn1.BerReader
/// --- API /// --- API
@ -52,6 +48,13 @@ function Parser (options = {}) {
} }
util.inherits(Parser, EventEmitter) 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) { Parser.prototype.write = function (data) {
if (!data || !Buffer.isBuffer(data)) { throw new TypeError('data (buffer) required') } if (!data || !Buffer.isBuffer(data)) { throw new TypeError('data (buffer) required') }
@ -64,9 +67,9 @@ Parser.prototype.write = function (data) {
return true 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 let foundSeq = false
try { try {
@ -80,9 +83,22 @@ Parser.prototype.write = function (data) {
return false return false
} else if (ber.remain > ber.length) { } else if (ber.remain > ber.length) {
// ETOOMUCH // 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) 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) assert.equal(ber.remain, ber.length)
} }
@ -92,13 +108,25 @@ Parser.prototype.write = function (data) {
let message let message
try { try {
if (Object.prototype.toString.call(ber) === '[object BerReader]') {
// Parse the BER into a JavaScript object representation. The message
// objects require the full sequence in order to construct the object.
// At this point, we have already read the sequence tag and length, so
// we need to rewind the buffer a bit. The `.sequenceToReader` method
// does this for us.
message = messages.LdapMessage.parse(ber.sequenceToReader())
} else {
// Bail here if peer isn't speaking protocol at all // Bail here if peer isn't speaking protocol at all
message = this.getMessage(ber) message = this.getMessage(ber)
}
if (!message) { if (!message) {
return end() 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) { } catch (e) {
this.emit('error', e, message) this.emit('error', e, message)
return false return false
@ -113,88 +141,88 @@ Parser.prototype.getMessage = function (ber) {
const self = this const self = this
const messageID = ber.readInt() const messageId = ber.readInt()
const type = ber.readSequence() const type = ber.readSequence()
let Message let Message
switch (type) { switch (type) {
case Protocol.LDAP_REQ_ABANDON: case Protocol.operations.LDAP_REQ_ABANDON:
Message = AbandonRequest Message = AbandonRequest
break break
case Protocol.LDAP_REQ_ADD: case Protocol.operations.LDAP_REQ_ADD:
Message = AddRequest Message = AddRequest
break break
case Protocol.LDAP_REP_ADD: case Protocol.operations.LDAP_RES_ADD:
Message = AddResponse Message = AddResponse
break break
case Protocol.LDAP_REQ_BIND: case Protocol.operations.LDAP_REQ_BIND:
Message = BindRequest Message = BindRequest
break break
case Protocol.LDAP_REP_BIND: case Protocol.operations.LDAP_RES_BIND:
Message = BindResponse Message = BindResponse
break break
case Protocol.LDAP_REQ_COMPARE: case Protocol.operations.LDAP_REQ_COMPARE:
Message = CompareRequest Message = CompareRequest
break break
case Protocol.LDAP_REP_COMPARE: case Protocol.operations.LDAP_RES_COMPARE:
Message = CompareResponse Message = CompareResponse
break break
case Protocol.LDAP_REQ_DELETE: case Protocol.operations.LDAP_REQ_DELETE:
Message = DeleteRequest Message = DeleteRequest
break break
case Protocol.LDAP_REP_DELETE: case Protocol.operations.LDAP_RES_DELETE:
Message = DeleteResponse Message = DeleteResponse
break break
case Protocol.LDAP_REQ_EXTENSION: case Protocol.operations.LDAP_REQ_EXTENSION:
Message = ExtendedRequest Message = ExtendedRequest
break break
case Protocol.LDAP_REP_EXTENSION: case Protocol.operations.LDAP_RES_EXTENSION:
Message = ExtendedResponse Message = ExtendedResponse
break break
case Protocol.LDAP_REQ_MODIFY: case Protocol.operations.LDAP_REQ_MODIFY:
Message = ModifyRequest Message = ModifyRequest
break break
case Protocol.LDAP_REP_MODIFY: case Protocol.operations.LDAP_RES_MODIFY:
Message = ModifyResponse Message = ModifyResponse
break break
case Protocol.LDAP_REQ_MODRDN: case Protocol.operations.LDAP_REQ_MODRDN:
Message = ModifyDNRequest Message = ModifyDNRequest
break break
case Protocol.LDAP_REP_MODRDN: case Protocol.operations.LDAP_RES_MODRDN:
Message = ModifyDNResponse Message = ModifyDNResponse
break break
case Protocol.LDAP_REQ_SEARCH: case Protocol.operations.LDAP_REQ_SEARCH:
Message = SearchRequest Message = SearchRequest
break break
case Protocol.LDAP_REP_SEARCH_ENTRY: case Protocol.operations.LDAP_RES_SEARCH_ENTRY:
Message = SearchEntry Message = SearchEntry
break break
case Protocol.LDAP_REP_SEARCH_REF: case Protocol.operations.LDAP_RES_SEARCH_REF:
Message = SearchReference Message = SearchReference
break break
case Protocol.LDAP_REP_SEARCH: case Protocol.operations.LDAP_RES_SEARCH:
Message = SearchResponse Message = SearchResponse
break break
case Protocol.LDAP_REQ_UNBIND: case Protocol.operations.LDAP_REQ_UNBIND:
Message = UnbindRequest Message = UnbindRequest
break break
@ -203,15 +231,15 @@ Parser.prototype.getMessage = function (ber) {
new Error('Op 0x' + (type ? type.toString(16) : '??') + new Error('Op 0x' + (type ? type.toString(16) : '??') +
' not supported'), ' not supported'),
new LDAPResult({ new LDAPResult({
messageID: messageID, messageId,
protocolOp: type || Protocol.LDAP_REP_EXTENSION protocolOp: type || Protocol.operations.LDAP_RES_EXTENSION
})) }))
return false return false
} }
return new Message({ return new Message({
messageID: messageID, messageId,
log: self.log 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. // Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus') const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result') const Attribute = require('@ldapjs/attribute')
const SearchEntry = require('./search_entry') const {
const SearchReference = require('./search_reference') SearchResultEntry: SearchEntry,
SearchResultReference: SearchReference,
SearchResultDone
} = require('@ldapjs/messages')
const dtrace = require('../dtrace') const parseDN = require('@ldapjs/dn').DN.fromString
const parseDN = require('../dn').parse
const parseURL = require('../url').parse
const Protocol = require('../protocol')
/// --- API /// --- API
function SearchResponse (options) { class SearchResponse extends SearchResultDone {
options = options || {} attributes
assert.object(options) notAttributes
sentEntries
options.protocolOp = Protocol.LDAP_REP_SEARCH constructor (options = {}) {
LDAPResult.call(this, options) super(options)
this.attributes = options.attributes ? options.attributes.slice() : [] this.attributes = options.attributes ? options.attributes.slice() : []
this.notAttributes = [] this.notAttributes = []
this.sentEntries = 0 this.sentEntries = 0
} }
util.inherits(SearchResponse, LDAPResult) }
/** /**
* Allows you to send a SearchEntry back to the client. * Allows you to send a SearchEntry back to the client.
@ -44,12 +44,16 @@ SearchResponse.prototype.send = function (entry, nofiltering) {
const savedAttrs = {} const savedAttrs = {}
let save = null let save = null
if (entry instanceof SearchEntry || entry instanceof SearchReference) { if (entry instanceof SearchEntry || entry instanceof SearchReference) {
if (!entry.messageID) { entry.messageID = this.messageID } if (!entry.messageId) { entry.messageId = this.messageId }
if (entry.messageID !== this.messageID) { throw new Error('SearchEntry messageID mismatch') } if (entry.messageId !== this.messageId) {
throw new Error('SearchEntry messageId mismatch')
}
} else { } else {
if (!entry.attributes) { throw new Error('entry.attributes required') } if (!entry.attributes) { throw new Error('entry.attributes required') }
const all = (self.attributes.indexOf('*') !== -1) 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) { Object.keys(entry.attributes).forEach(function (a) {
const _a = a.toLowerCase() const _a = a.toLowerCase()
if (!nofiltering && _a.length && _a[0] === '_') { if (!nofiltering && _a.length && _a[0] === '_') {
@ -69,39 +73,24 @@ SearchResponse.prototype.send = function (entry, nofiltering) {
save = entry save = entry
entry = new SearchEntry({ entry = new SearchEntry({
objectName: typeof (save.dn) === 'string' ? parseDN(save.dn) : save.dn, objectName: typeof (save.dn) === 'string' ? parseDN(save.dn) : save.dn,
messageID: self.messageID, messageId: self.messageId,
log: self.log attributes: Attribute.fromObject(entry.attributes)
}) })
entry.fromObject(save)
} }
try { 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++ 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 // Restore attributes
Object.keys(savedAttrs).forEach(function (k) { Object.keys(savedAttrs).forEach(function (k) {
save.attributes[k] = savedAttrs[k] save.attributes[k] = savedAttrs[k]
}) })
} catch (e) { } catch (e) {
this.log.warn(e, '%s failure to write message %j', 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) assert.object(object)
const entry = new SearchEntry({ const entry = new SearchEntry({
messageID: this.messageID, messageId: this.messageId,
log: this.log, objectName: object.objectName || object.dn,
objectName: object.objectName || object.dn attributes: object.attributes ?? []
}) })
entry.fromObject((object.attributes || object))
return entry return entry
} }
@ -122,15 +110,10 @@ SearchResponse.prototype.createSearchReference = function (uris) {
if (!Array.isArray(uris)) { uris = [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 const self = this
return new SearchReference({ return new SearchReference({
messageID: self.messageID, messageId: self.messageId,
log: self.log, uri: uris
uris: 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 value.changeNumber = attrs.changenumber
return new EntryChangeNotificationControl({ value: value }) return new EntryChangeNotificationControl({ value })
} else { } else {
return false return false
} }
@ -104,6 +104,6 @@ function checkChangeType (req, requestType) {
module.exports = { module.exports = {
PersistentSearchCache: PersistentSearch, PersistentSearchCache: PersistentSearch,
checkChangeType: checkChangeType, checkChangeType,
getEntryChangeNotificationControl: getEntryChangeNotificationControl 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 tls = require('tls')
const util = require('util') const util = require('util')
// var asn1 = require('asn1') // var asn1 = require('@ldapjs/asn1')
const VError = require('verror').VError const VError = require('verror').VError
const dn = require('./dn') const { DN, RDN } = require('@ldapjs/dn')
const dtrace = require('./dtrace')
const errors = require('./errors') const errors = require('./errors')
const Protocol = require('./protocol') const Protocol = require('@ldapjs/protocol')
const messages = require('@ldapjs/messages')
const Parser = require('./messages').Parser const Parser = require('./messages').Parser
const AbandonResponse = require('./messages/abandon_response') const LdapResult = messages.LdapResult
const AddResponse = require('./messages/add_response') const AbandonResponse = messages.AbandonResponse
const BindResponse = require('./messages/bind_response') const AddResponse = messages.AddResponse
const CompareResponse = require('./messages/compare_response') const BindResponse = messages.BindResponse
const DeleteResponse = require('./messages/del_response') const CompareResponse = messages.CompareResponse
const ExtendedResponse = require('./messages/ext_response') const DeleteResponse = messages.DeleteResponse
// var LDAPResult = require('./messages/result') const ExtendedResponse = messages.ExtensionResponse
const ModifyResponse = require('./messages/modify_response') const ModifyResponse = messages.ModifyResponse
const ModifyDNResponse = require('./messages/moddn_response') const ModifyDnResponse = messages.ModifyDnResponse
const SearchRequest = require('./messages/search_request') const SearchRequest = messages.SearchRequest
const SearchResponse = require('./messages/search_response') const SearchResponse = require('./messages/search_response')
const UnbindResponse = require('./messages/unbind_response')
/// --- Globals /// --- Globals
// var Ber = asn1.Ber // var Ber = asn1.Ber
// var BerReader = asn1.BerReader // var BerReader = asn1.BerReader
const DN = dn.DN // const DN = dn.DN
// var sprintf = util.format // var sprintf = util.format
@ -47,15 +47,15 @@ function mergeFunctionArgs (argv, start, end) {
const handlers = [] const handlers = []
for (let i = start; i < end; i++) { for (let i = start; i < end; i++) {
if (argv[i] instanceof Array) { if (Array.isArray(argv[i])) {
const arr = argv[i] const arr = argv[i]
for (let j = 0; j < arr.length; j++) { 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])) throw new TypeError('Invalid argument type: ' + typeof (arr[j]))
} }
handlers.push(arr[j]) handlers.push(arr[j])
} }
} else if (argv[i] instanceof Function) { } else if (typeof argv[i] === 'function') {
handlers.push(argv[i]) handlers.push(argv[i])
} else { } else {
throw new TypeError('Invalid argument type: ' + typeof (argv[i])) throw new TypeError('Invalid argument type: ' + typeof (argv[i]))
@ -71,35 +71,42 @@ function getResponse (req) {
let Response let Response
switch (req.protocolOp) { switch (req.protocolOp) {
case Protocol.LDAP_REQ_BIND: case Protocol.operations.LDAP_REQ_BIND:
Response = BindResponse Response = BindResponse
break break
case Protocol.LDAP_REQ_ABANDON: case Protocol.operations.LDAP_REQ_ABANDON:
Response = AbandonResponse Response = AbandonResponse
break break
case Protocol.LDAP_REQ_ADD: case Protocol.operations.LDAP_REQ_ADD:
Response = AddResponse Response = AddResponse
break break
case Protocol.LDAP_REQ_COMPARE: case Protocol.operations.LDAP_REQ_COMPARE:
Response = CompareResponse Response = CompareResponse
break break
case Protocol.LDAP_REQ_DELETE: case Protocol.operations.LDAP_REQ_DELETE:
Response = DeleteResponse Response = DeleteResponse
break break
case Protocol.LDAP_REQ_EXTENSION: case Protocol.operations.LDAP_REQ_EXTENSION:
Response = ExtendedResponse Response = ExtendedResponse
break break
case Protocol.LDAP_REQ_MODIFY: case Protocol.operations.LDAP_REQ_MODIFY:
Response = ModifyResponse Response = ModifyResponse
break break
case Protocol.LDAP_REQ_MODRDN: case Protocol.operations.LDAP_REQ_MODRDN:
Response = ModifyDNResponse Response = ModifyDnResponse
break break
case Protocol.LDAP_REQ_SEARCH: case Protocol.operations.LDAP_REQ_SEARCH:
Response = SearchResponse Response = SearchResponse
break break
case Protocol.LDAP_REQ_UNBIND: case Protocol.operations.LDAP_REQ_UNBIND:
Response = UnbindResponse // 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 break
default: default:
return null return null
@ -107,16 +114,83 @@ function getResponse (req) {
assert.ok(Response) assert.ok(Response)
const res = new Response({ const res = new Response({
messageID: req.messageID, messageId: req.messageId,
log: req.log,
attributes: ((req instanceof SearchRequest) ? req.attributes : undefined) attributes: ((req instanceof SearchRequest) ? req.attributes : undefined)
}) })
res.log = req.log
res.connection = req.connection res.connection = req.connection
res.logId = req.logId 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 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) { function defaultHandler (req, res, next) {
assert.ok(req) assert.ok(req)
assert.ok(res) assert.ok(res)
@ -157,69 +231,6 @@ function noExOpHandler (req, res, next) {
return 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 /// --- API
/** /**
@ -261,8 +272,6 @@ function Server (options) {
this._chain = [] this._chain = []
this.log = options.log this.log = options.log
this.strictDN = (options.strictDN !== undefined) ? options.strictDN : true
const log = this.log const log = this.log
function setupConnection (c) { function setupConnection (c) {
@ -277,12 +286,12 @@ function Server (options) {
c.remotePort = c.socket.remotePort c.remotePort = c.socket.remotePort
} }
const rdn = new dn.RDN({ cn: 'anonymous' }) const rdn = new RDN({ cn: 'anonymous' })
c.ldap = { c.ldap = {
id: c.remoteAddress + ':' + c.remotePort, id: c.remoteAddress + ':' + c.remotePort,
config: options, config: options,
_bindDN: new DN([rdn]) _bindDN: new DN({ rdns: [rdn] })
} }
c.addListener('timeout', function () { c.addListener('timeout', function () {
log.trace('%s timed out', c.ldap.id) log.trace('%s timed out', c.ldap.id)
@ -305,7 +314,9 @@ function Server (options) {
return c.ldap._bindDN return c.ldap._bindDN
}) })
c.ldap.__defineSetter__('bindDN', function (val) { 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 c.ldap._bindDN = val
return val return val
@ -319,19 +330,17 @@ function Server (options) {
setupConnection(conn) setupConnection(conn)
log.trace('new connection from %s', conn.ldap.id) log.trace('new connection from %s', conn.ldap.id)
dtrace.fire('server-connection', function () {
return [conn.remoteAddress]
})
conn.parser = new Parser({ conn.parser = new Parser({
log: options.log log: options.log
}) })
conn.parser.on('message', function (req) { 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.connection = conn
req.logId = conn.ldap.id + '::' + req.messageID req.logId = conn.ldap.id + '::' + req.messageId
req.startTime = new Date().getTime() 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) const res = getResponse(req)
if (!res) { if (!res) {
@ -343,32 +352,48 @@ function Server (options) {
// parse string DNs for routing/etc // parse string DNs for routing/etc
try { try {
switch (req.protocolOp) { switch (req.protocolOp) {
case Protocol.LDAP_REQ_BIND: case Protocol.operations.LDAP_REQ_BIND: {
req.name = dn.parse(req.name) req.name = DN.fromString(req.name)
break break
case Protocol.LDAP_REQ_ADD: }
case Protocol.LDAP_REQ_COMPARE:
case Protocol.LDAP_REQ_DELETE: case Protocol.operations.LDAP_REQ_ADD:
req.entry = dn.parse(req.entry) 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 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 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 // TODO: handle newRdn/Superior
break break
case Protocol.LDAP_REQ_SEARCH: }
req.baseObject = dn.parse(req.baseObject)
case Protocol.operations.LDAP_REQ_SEARCH: {
break break
default: }
default: {
break break
} }
}
} catch (e) { } catch (e) {
if (self.strictDN) {
return res.end(errors.LDAP_INVALID_DN_SYNTAX) return res.end(errors.LDAP_INVALID_DN_SYNTAX)
} }
}
res.connection = conn res.connection = conn
res.logId = req.logId res.logId = req.logId
@ -406,19 +431,19 @@ function Server (options) {
const next = messageIIFE const next = messageIIFE
if (chain.handlers[i]) { return chain.handlers[i++].call(chain.backend, req, res, next) } 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 // 0 length == anonymous bind
if (req.dn.length === 0 && req.credentials === '') { 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 { } else {
conn.ldap.bindDN = req.dn conn.ldap.bindDN = DN.fromString(req.dn)
} }
} }
// unbind clear bindDN for safety // unbind clear bindDN for safety
// conn should terminate on unbind (RFC4511 4.3) // conn should terminate on unbind (RFC4511 4.3)
if (req.protocolOp === Protocol.LDAP_REQ_UNBIND && res.status === 0) { if (req.protocolOp === Protocol.operations.LDAP_REQ_UNBIND && res.status === 0) {
conn.ldap.bindDN = new DN([new dn.RDN({ cn: 'anonymous' })]) conn.ldap.bindDN = new DN({ rdns: [new RDN({ cn: 'anonymous' })] })
} }
return after() return after()
@ -508,7 +533,15 @@ Object.defineProperties(Server.prototype, {
} else { } else {
str = 'ldap://' 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 return str
}, },
configurable: false configurable: false
@ -528,7 +561,7 @@ module.exports = Server
*/ */
Server.prototype.add = function (name) { Server.prototype.add = function (name) {
const args = Array.prototype.slice.call(arguments, 1) 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) { Server.prototype.bind = function (name) {
const args = Array.prototype.slice.call(arguments, 1) 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) { Server.prototype.compare = function (name) {
const args = Array.prototype.slice.call(arguments, 1) 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) { Server.prototype.del = function (name) {
const args = Array.prototype.slice.call(arguments, 1) 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) { Server.prototype.exop = function (name) {
const args = Array.prototype.slice.call(arguments, 1) 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) { Server.prototype.modify = function (name) {
const args = Array.prototype.slice.call(arguments, 1) 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) { Server.prototype.modifyDN = function (name) {
const args = Array.prototype.slice.call(arguments, 1) 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) { Server.prototype.search = function (name) {
const args = Array.prototype.slice.call(arguments, 1) 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 () { Server.prototype.unbind = function () {
const args = Array.prototype.slice.call(arguments, 0) 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 () { 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) { Server.prototype.listen = function (port, host, callback) {
if (typeof (port) !== 'number' && typeof (port) !== 'string') { throw new TypeError('port (number or path) required') } if (typeof (port) !== 'number' && typeof (port) !== 'string') { throw new TypeError('port (number or path) required') }
if (typeof (host) === 'function') { if (typeof (host) === 'function') {
callback = host callback = host
host = '0.0.0.0' host = '127.0.0.1'
} }
if (typeof (port) === 'string' && /^[0-9]+$/.test(port)) { if (typeof (port) === 'string' && /^[0-9]+$/.test(port)) {
// Disambiguate between string ports and file paths // Disambiguate between string ports and file paths
@ -717,12 +750,10 @@ Server.prototype.getConnections = function (callback) {
} }
Server.prototype._getRoute = function (_dn, backend) { Server.prototype._getRoute = function (_dn, backend) {
assert.ok(dn)
if (!backend) { backend = this } if (!backend) { backend = this }
let name let name
if (_dn instanceof dn.DN) { if (Object.prototype.toString.call(_dn) === '[object LdapDn]') {
name = _dn.toString() name = _dn.toString()
} else { } else {
name = _dn name = _dn
@ -749,10 +780,10 @@ Server.prototype._sortedRouteKeys = function _sortedRouteKeys () {
Object.keys(this.routes).forEach(function (key) { Object.keys(this.routes).forEach(function (key) {
const _dn = self.routes[key].dn const _dn = self.routes[key].dn
// Ignore non-DN routes such as exop or unbind // 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() const reversed = _dn.clone()
reversed.rdns.reverse() reversed.reverse()
reversedRDNsToKeys[reversed.format()] = key reversedRDNsToKeys[reversed.toString()] = key
} }
}) })
const output = [] const output = []
@ -769,17 +800,15 @@ Server.prototype._sortedRouteKeys = function _sortedRouteKeys () {
return this._routeKeyCache return this._routeKeyCache
} }
Server.prototype._getHandlerChain = function _getHandlerChain (req, res) { Server.prototype._getHandlerChain = function _getHandlerChain (req) {
assert.ok(req) assert.ok(req)
fireDTraceProbe(req, res)
const self = this const self = this
const routes = this.routes const routes = this.routes
let route let route
// check anonymous bind // check anonymous bind
if (req.protocolOp === Protocol.LDAP_REQ_BIND && if (req.protocolOp === Protocol.operations.LDAP_REQ_BIND &&
req.dn.toString() === '' && req.dn.toString() === '' &&
req.credentials === '') { req.credentials === '') {
return { return {
@ -791,7 +820,7 @@ Server.prototype._getHandlerChain = function _getHandlerChain (req, res) {
const op = '0x' + req.protocolOp.toString(16) const op = '0x' + req.protocolOp.toString(16)
// Special cases are exops, unbinds and abandons. Handle those first. // 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] route = routes[req.requestName]
if (route) { if (route) {
return { return {
@ -804,7 +833,7 @@ Server.prototype._getHandlerChain = function _getHandlerChain (req, res) {
handlers: [noExOpHandler] handlers: [noExOpHandler]
} }
} }
} else if (req.protocolOp === Protocol.LDAP_REQ_UNBIND) { } else if (req.protocolOp === Protocol.operations.LDAP_REQ_UNBIND) {
route = routes.unbind route = routes.unbind
if (route) { if (route) {
return { return {
@ -817,7 +846,7 @@ Server.prototype._getHandlerChain = function _getHandlerChain (req, res) {
handlers: [defaultNoOpHandler] handlers: [defaultNoOpHandler]
} }
} }
} else if (req.protocolOp === Protocol.LDAP_REQ_ABANDON) { } else if (req.protocolOp === Protocol.operations.LDAP_REQ_ABANDON) {
return { return {
backend: self, backend: self,
handlers: [defaultNoOpHandler] handlers: [defaultNoOpHandler]
@ -825,11 +854,11 @@ Server.prototype._getHandlerChain = function _getHandlerChain (req, res) {
} }
// Otherwise, match via DN rules // Otherwise, match via DN rules
assert.ok(req.dn)
const keys = this._sortedRouteKeys() const keys = this._sortedRouteKeys()
let fallbackHandler = [noSuffixHandler] let fallbackHandler = [noSuffixHandler]
// invalid DNs in non-strict mode are routed to the default handler // 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++) { for (let i = 0; i < keys.length; i++) {
const suffix = keys[i] const suffix = keys[i]
@ -876,7 +905,7 @@ Server.prototype._mount = function (op, name, argv, notDN) {
backend = argv[0] backend = argv[0]
index = 1 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() const chain = this._chain.slice()
argv.slice(index).forEach(function (a) { argv.slice(index).forEach(function (a) {

View File

@ -2,8 +2,8 @@
const querystring = require('querystring') const querystring = require('querystring')
const url = require('url') const url = require('url')
const dn = require('./dn') const { DN } = require('@ldapjs/dn')
const filter = require('./filters/') const filter = require('@ldapjs/filter')
module.exports = { module.exports = {
@ -38,7 +38,7 @@ module.exports = {
if (u.pathname) { if (u.pathname) {
u.pathname = querystring.unescape(u.pathname.substr(1)) 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) { if (u.search) {

View File

@ -3,57 +3,57 @@
"name": "ldapjs", "name": "ldapjs",
"homepage": "http://ldapjs.org", "homepage": "http://ldapjs.org",
"description": "LDAP client and server APIs", "description": "LDAP client and server APIs",
"version": "2.3.2", "version": "3.0.7",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/ldapjs/node-ldapjs.git" "url": "git://github.com/ldapjs/node-ldapjs.git"
}, },
"main": "lib/index.js", "main": "lib/index.js",
"directories": {
"lib": "./lib"
},
"engines": {
"node": ">=10.13.0"
},
"dependencies": { "dependencies": {
"abstract-logging": "^2.0.0", "@ldapjs/asn1": "^2.0.0",
"asn1": "^0.2.4", "@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", "assert-plus": "^1.0.0",
"backoff": "^2.5.0", "backoff": "^2.5.0",
"ldap-filter": "^0.3.3",
"once": "^1.4.0", "once": "^1.4.0",
"vasync": "^2.2.0", "vasync": "^2.2.1",
"verror": "^1.8.1" "verror": "^1.10.1"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^7.20.0", "@fastify/pre-commit": "^2.0.2",
"eslint-config-standard": "^16.0.2", "eslint": "^8.44.0",
"eslint-plugin-import": "^2.22.1", "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-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0", "eslint-plugin-promise": "6.1.1",
"front-matter": "^4.0.2", "front-matter": "^4.0.2",
"get-port": "^5.1.1", "get-port": "^5.1.1",
"highlight.js": "^11.0.1", "highlight.js": "^11.7.0",
"husky": "^4.2.5", "marked": "^4.2.12",
"marked": "^4.0.0", "tap": "^16.3.7"
"tap": "15.1.6"
}, },
"scripts": { "scripts": {
"test": "tap --no-cov", "test": "tap --no-cov -R terse",
"test:ci": "tap --coverage-report=lcovonly", "test:ci": "tap --coverage-report=lcovonly -R terse",
"test:cov": "tap", "test:cov": "tap -R terse",
"test:cov:html": "tap --coverage-report=html", "test:cov:html": "tap --coverage-report=html -R terse",
"test:watch": "tap -n -w --no-coverage-report", "test:watch": "tap -n -w --no-coverage-report -R terse",
"test:integration": "tap --no-cov 'test-integration/**/*.test.js'", "test:integration": "tap --no-cov -R terse 'test-integration/**/*.test.js'",
"test:integration:local": "docker-compose up -d && npm run test:integration && docker-compose down", "test:integration:local": "docker-compose up -d --wait && npm run test:integration ; docker-compose down",
"lint": "eslint . --fix", "lint": "eslint . --fix",
"lint:ci": "eslint .", "lint:ci": "eslint .",
"docs": "node scripts/build-docs.js" "docs": "node scripts/build-docs.js"
}, },
"husky": { "pre-commit": [
"hooks": { "lint:ci",
"pre-commit": "npm run lint:ci && npm run test" "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}` const baseURL = `${SCHEME}://${HOST}:${PORT}`
tap.test('modifyDN with long name (issue #480)', t => { 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 targetDN = 'cn=Turanga Leela,ou=people,dc=planetexpress,dc=com'
const client = ldapjs.createClient({ url: baseURL }) const client = ldapjs.createClient({ url: baseURL })
client.bind('cn=admin,dc=planetexpress,dc=com', 'GoodNewsEveryone', bindHandler) 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) => { client.exop('1.3.6.1.4.1.4203.1.11.3', (err, value, res) => {
t.error(err) t.error(err)
t.ok(value) 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.ok(res)
t.is(res.status, 0) t.equal(res.status, 0)
client.unbind(t.end) client.unbind(t.end)
}) })
@ -74,15 +81,15 @@ tap.test('can access large groups (issue #582)', t => {
}) })
response.on('error', t.error) response.on('error', t.error)
response.on('end', (result) => { response.on('end', (result) => {
t.is(result.status, 0) t.equal(result.status, 0)
t.is(results.length === 1, true) t.equal(results.length === 1, true)
t.ok(results[0].attributes) t.ok(results[0].attributes)
const memberAttr = results[0].attributes.find(a => a.type === 'member') const memberAttr = results[0].attributes.find(a => a.type === 'member')
t.ok(memberAttr) t.ok(memberAttr)
t.ok(memberAttr.vals) t.ok(memberAttr.values)
t.type(memberAttr.vals, Array) t.type(memberAttr.values, Array)
t.is(memberAttr.vals.length, 2000) t.equal(memberAttr.values.length, 2000)
client.unbind(t.end) client.unbind(t.end)
}) })

View File

@ -1,4 +1,8 @@
module.exports = { module.exports = {
parserOptions: {
ecmaVersion: 'latest'
},
rules: { rules: {
'no-shadow': 'off' '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 vasync = require('vasync')
const getPort = require('get-port') const getPort = require('get-port')
const { getSock, uuid } = require('./utils') 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 ldap = require('../lib')
const { Attribute, Change } = ldap
const {
SearchRequest,
SearchResultEntry,
SearchResultReference,
SearchResultDone
} = messages
const SUFFIX = 'dc=test' const SUFFIX = 'dc=test'
const LDAP_CONNECT_TIMEOUT = process.env.LDAP_CONNECT_TIMEOUT || 0 const LDAP_CONNECT_TIMEOUT = process.env.LDAP_CONNECT_TIMEOUT || 0
@ -44,7 +55,7 @@ tap.beforeEach((t) => {
// LDAP whoami // LDAP whoami
server.exop('1.3.6.1.4.1.4203.1.11.3', function (req, res, next) { 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() res.end()
return next() return next()
}) })
@ -88,21 +99,21 @@ tap.beforeEach((t) => {
if (req.dn.equals('cn=ref,' + SUFFIX)) { if (req.dn.equals('cn=ref,' + SUFFIX)) {
res.send(res.createSearchReference('ldap://localhost')) res.send(res.createSearchReference('ldap://localhost'))
} else if (req.dn.equals('cn=bin,' + SUFFIX)) { } 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({ res.send(res.createSearchEntry({
objectName: req.dn, objectName: req.dn,
attributes: { attributes
'foo;binary': 'wr0gKyDCvCA9IMK+',
gb18030: Buffer.from([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA]),
objectclass: 'binary'
}
})) }))
} else { } else {
const attributes = []
attributes.push(new Attribute({ type: 'cn', values: ['unit', 'test'] }))
attributes.push(new Attribute({ type: 'SN', values: ['testy'] }))
const e = res.createSearchEntry({ const e = res.createSearchEntry({
objectName: req.dn, objectName: req.dn,
attributes: { attributes
cn: ['unit', 'test'],
SN: 'testy'
}
}) })
res.send(e) res.send(e)
res.send(e) res.send(e)
@ -142,13 +153,14 @@ tap.beforeEach((t) => {
end = (end > max || end < min) ? max : end end = (end > max || end < min) ? max : end
let i let i
for (i = start; i < end; i++) { for (i = start; i < end; i++) {
res.send({ res.send(new SearchResultEntry({
dn: util.format('o=%d, cn=paged', i), messageId: res.id,
attributes: { entry: `o=${i},cn=paged`,
attributes: Attribute.fromObject({
o: [i], o: [i],
objectclass: ['pagedResult'] objectclass: ['pagedResult']
}
}) })
}))
} }
return i return i
} }
@ -156,13 +168,17 @@ tap.beforeEach((t) => {
let cookie = null let cookie = null
let pageSize = 0 let pageSize = 0
req.controls.forEach(function (control) { req.controls.forEach(function (control) {
if (control.type === ldap.PagedResultsControl.OID) { if (control.type === controls.PagedResultsControl.OID) {
pageSize = control.value.size pageSize = control.value.size
cookie = control.value.cookie cookie = control.value.cookie
} }
}) })
if (cookie && Buffer.isBuffer(cookie)) { if (!cookie || Buffer.isBuffer(cookie) === false) {
// don't allow non-paged searches for this test endpoint
next(Error('unwilling to perform'))
}
// Do simple paging // Do simple paging
let first = min let first = min
if (cookie.length !== 0) { if (cookie.length !== 0) {
@ -176,7 +192,7 @@ tap.beforeEach((t) => {
} else { } else {
resultCookie = Buffer.from('') resultCookie = Buffer.from('')
} }
res.controls.push(new ldap.PagedResultsControl({ res.addControl(new controls.PagedResultsControl({
value: { value: {
size: pageSize, // correctness not required here size: pageSize, // correctness not required here
cookie: resultCookie cookie: resultCookie
@ -184,11 +200,8 @@ tap.beforeEach((t) => {
})) }))
res.end() res.end()
next() next()
} else {
// don't allow non-paged searches for this test endpoint
next(new ldap.UnwillingToPerformError())
}
}) })
server.search('cn=sssvlv', function (req, res, next) { server.search('cn=sssvlv', function (req, res, next) {
const min = 0 const min = 0
const max = 100 const max = 100
@ -484,7 +497,7 @@ tap.test('add success', function (t) {
const attrs = [ const attrs = [
new Attribute({ new Attribute({
type: 'cn', type: 'cn',
vals: ['test'] values: ['test']
}) })
] ]
t.context.client.add('cn=add, ' + SUFFIX, attrs, function (err, res) { t.context.client.add('cn=add, ' + SUFFIX, attrs, function (err, res) {
@ -509,7 +522,7 @@ tap.test('add success with object', function (t) {
}) })
tap.test('add buffer', function (t) { tap.test('add buffer', function (t) {
const { BerReader } = require('asn1') const { BerReader } = require('@ldapjs/asn1')
const dn = `cn=add,${SUFFIX}` const dn = `cn=add,${SUFFIX}`
const attribute = 'thumbnailPhoto' const attribute = 'thumbnailPhoto'
const binary = 0xa5 const binary = 0xa5
@ -519,7 +532,7 @@ tap.test('add buffer', function (t) {
const write = t.context.client._socket.write const write = t.context.client._socket.write
t.context.client._socket.write = (data, encoding, cb) => { t.context.client._socket.write = (data, encoding, cb) => {
const reader = new BerReader(data) const reader = new BerReader(data)
t.equal(data.byteLength, 49) t.equal(data.byteLength, 48)
t.ok(reader.readSequence()) t.ok(reader.readSequence())
t.equal(reader.readInt(), 0x1) t.equal(reader.readInt(), 0x1)
t.equal(reader.readSequence(), 0x68) t.equal(reader.readSequence(), 0x68)
@ -621,7 +634,7 @@ tap.test('modify success', function (t) {
type: 'Replace', type: 'Replace',
modification: new Attribute({ modification: new Attribute({
type: 'cn', type: 'cn',
vals: ['test'] values: ['test']
}) })
}) })
t.context.client.modify('cn=modify, ' + SUFFIX, change, function (err, res) { 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 // https://github.com/ldapjs/node-ldapjs/pull/435
tap.test('can delete attributes', function (t) { tap.test('can delete attributes', function (t) {
const change = new Change({ const change = new Change({
type: 'Delete', type: 'Delete',
modification: { cn: null } modification: new Attribute({ type: 'cn', values: [null] })
}) })
t.context.client.modify('cn=modify,' + SUFFIX, change, function (err, res) { t.context.client.modify('cn=modify,' + SUFFIX, change, function (err, res) {
t.error(err) t.error(err)
@ -667,7 +665,7 @@ tap.test('modify array success', function (t) {
operation: 'Replace', operation: 'Replace',
modification: new Attribute({ modification: new Attribute({
type: 'cn', type: 'cn',
vals: ['test'] values: ['test']
}) })
}), }),
new Change({ 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) { tap.test('modify DN new RDN only', function (t) {
t.context.client.modifyDN('cn=old, ' + SUFFIX, 'cn=new', function (err, res) { t.context.client.modifyDN('cn=old, ' + SUFFIX, 'cn=new', function (err, res) {
t.error(err) 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) { tap.test('modify DN excessive superior length', function (t) {
const { BerReader, BerWriter } = require('asn1') const { ModifyDnRequest } = messages
const ModifyDNRequest = require('../lib/messages/moddn_request')
const ber = new BerWriter()
const entry = 'cn=Test User,ou=A Long OU ,ou=Another Long OU ,ou=Another Long OU ,dc=acompany,DC=io' 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 newSuperior = 'ou=A New Long OU , ou=Another New Long OU , ou=An OU , dc=acompany, dc=io'
const newRdn = entry.replace(/(.*?),.*/, '$1') const newRdn = entry.replace(/(.*?),.*/, '$1')
const deleteOldRdn = true const deleteOldRdn = true
const req = new ModifyDNRequest({
entry: entry, const req = new ModifyDnRequest({
deleteOldRdn: deleteOldRdn, entry,
controls: [] deleteOldRdn,
newRdn,
newSuperior
}) })
req.newRdn = newRdn
req.newSuperior = 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')
req._toBer(ber) t.equal(req.newRdn.toString(), 'cn=Test User')
const reader = new BerReader(ber.buffer) t.equal(req.deleteOldRdn, true)
t.equal(reader.readString(), entry) t.equal(req.newSuperior.toString(), 'ou=A New Long OU,ou=Another New Long OU,ou=An OU,dc=acompany,dc=io')
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.end() t.end()
}) })
tap.test('search basic', function (t) { tap.test('search basic', function (t) {
const { SearchResultEntry, SearchResultDone } = messages
t.context.client.search('cn=test, ' + SUFFIX, '(objectclass=*)', function (err, res) { t.context.client.search('cn=test, ' + SUFFIX, '(objectclass=*)', function (err, res) {
t.error(err) t.error(err)
t.ok(res) t.ok(res)
let gotEntry = 0 let gotEntry = 0
res.on('searchEntry', function (entry) { res.on('searchEntry', function (entry) {
t.ok(entry) t.ok(entry)
t.ok(entry instanceof ldap.SearchEntry) t.ok(entry instanceof SearchResultEntry)
t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX) t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
t.ok(entry.attributes) t.ok(entry.attributes)
t.ok(entry.attributes.length) t.ok(entry.attributes.length)
t.equal(entry.attributes[0].type, 'cn') t.equal(entry.attributes[0].type, 'cn')
t.equal(entry.attributes[1].type, 'SN') t.equal(entry.attributes[1].type, 'SN')
t.ok(entry.object)
gotEntry++ gotEntry++
}) })
res.on('error', function (err) { res.on('error', function (err) {
@ -777,7 +754,37 @@ tap.test('search basic', function (t) {
}) })
res.on('end', function (res) { res.on('end', function (res) {
t.ok(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(res.status, 0)
t.equal(gotEntry, 2) t.equal(gotEntry, 2)
t.end() t.end()
@ -844,7 +851,7 @@ tap.test('search paged', { timeout: 10000 }, function (t) {
t2.error(err) t2.error(err)
res.on('searchEntry', entryListener) res.on('searchEntry', entryListener)
res.on('searchRequest', (searchRequest) => { res.on('searchRequest', (searchRequest) => {
t2.ok(searchRequest instanceof ldap.SearchRequest) t2.ok(searchRequest instanceof SearchRequest)
if (currentSearchRequest === null) { if (currentSearchRequest === null) {
t2.equal(countPages, 0) t2.equal(countPages, 0)
} }
@ -855,7 +862,7 @@ tap.test('search paged', { timeout: 10000 }, function (t) {
res.on('end', function (result) { res.on('end', function (result) {
t2.equal(countEntries, 1000) t2.equal(countEntries, 1000)
t2.equal(countPages, 10) t2.equal(countPages, 10)
t2.equal(result.messageID, currentSearchRequest.messageID) t2.equal(result.messageId, currentSearchRequest.messageId)
t2.end() t2.end()
}) })
@ -871,7 +878,7 @@ tap.test('search paged', { timeout: 10000 }, function (t) {
function pageListener (result) { function pageListener (result) {
countPages += 1 countPages += 1
if (countPages < 10) { if (countPages < 10) {
t2.equal(result.messageID, currentSearchRequest.messageID) t2.equal(result.messageId, currentSearchRequest.messageId)
} }
} }
}) })
@ -995,7 +1002,12 @@ tap.test('search paged', { timeout: 10000 }, function (t) {
t.end() 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) { t.test('ssv - asc', function (t2) {
let preventry = null let preventry = null
const sssrcontrol = new ldap.ServerSideSortingRequestControl( const sssrcontrol = new ldap.ServerSideSortingRequestControl(
@ -1025,6 +1037,7 @@ tap.test('search - sssvlv', { timeout: 10000 }, function (t) {
}) })
}) })
}) })
t.test('ssv - desc', function (t2) { t.test('ssv - desc', function (t2) {
let preventry = null let preventry = null
const sssrcontrol = new ldap.ServerSideSortingRequestControl( 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( const sssrcontrol = new ldap.ServerSideSortingRequestControl(
{ {
value: { 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( const sssrcontrol = new ldap.ServerSideSortingRequestControl(
{ {
value: { value: {
@ -1139,6 +1157,7 @@ tap.test('search - sssvlv', { timeout: 10000 }, function (t) {
}) })
}) })
}) })
t.end() t.end()
}) })
@ -1154,7 +1173,7 @@ tap.test('search referral', function (t) {
res.on('searchReference', function (referral) { res.on('searchReference', function (referral) {
gotReferral = true gotReferral = true
t.ok(referral) t.ok(referral)
t.ok(referral instanceof ldap.SearchReference) t.ok(referral instanceof SearchResultReference)
t.ok(referral.uris) t.ok(referral.uris)
t.ok(referral.uris.length) t.ok(referral.uris.length)
}) })
@ -1163,7 +1182,7 @@ tap.test('search referral', function (t) {
}) })
res.on('end', function (res) { res.on('end', function (res) {
t.ok(res) t.ok(res)
t.ok(res instanceof ldap.SearchResponse) t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0) t.equal(res.status, 0)
t.equal(gotEntry, 0) t.equal(gotEntry, 0)
t.ok(gotReferral) t.ok(gotReferral)
@ -1180,14 +1199,13 @@ tap.test('search rootDSE', function (t) {
t.ok(entry) t.ok(entry)
t.equal(entry.dn.toString(), '') t.equal(entry.dn.toString(), '')
t.ok(entry.attributes) t.ok(entry.attributes)
t.ok(entry.object)
}) })
res.on('error', function (err) { res.on('error', function (err) {
t.fail(err) t.fail(err)
}) })
res.on('end', function (res) { res.on('end', function (res) {
t.ok(res) t.ok(res)
t.ok(res instanceof ldap.SearchResponse) t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0) t.equal(res.status, 0)
t.end() t.end()
}) })
@ -1200,12 +1218,16 @@ tap.test('search empty attribute', function (t) {
t.ok(res) t.ok(res)
let gotEntry = 0 let gotEntry = 0
res.on('searchEntry', function (entry) { res.on('searchEntry', function (entry) {
const obj = entry.toObject() const obj = entry.pojo
t.equal('dc=empty', obj.dn) t.equal('dc=empty', obj.objectName)
t.ok(obj.member)
t.equal(obj.member.length, 0) const member = entry.attributes[0]
t.ok(obj['member;range=0-1']) t.ok(member)
t.ok(obj['member;range=0-1'].length) 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++ gotEntry++
}) })
res.on('error', function (err) { res.on('error', function (err) {
@ -1213,7 +1235,7 @@ tap.test('search empty attribute', function (t) {
}) })
res.on('end', function (res) { res.on('end', function (res) {
t.ok(res) t.ok(res)
t.ok(res instanceof ldap.SearchResponse) t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0) t.equal(res.status, 0)
t.equal(gotEntry, 1) t.equal(gotEntry, 1)
t.end() t.end()
@ -1230,12 +1252,12 @@ tap.test('GH-21 binary attributes', function (t) {
const expect2 = Buffer.from([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA]) const expect2 = Buffer.from([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA])
res.on('searchEntry', function (entry) { res.on('searchEntry', function (entry) {
t.ok(entry) t.ok(entry)
t.ok(entry instanceof ldap.SearchEntry) t.ok(entry instanceof SearchResultEntry)
t.equal(entry.dn.toString(), 'cn=bin,' + SUFFIX) t.equal(entry.dn.toString(), 'cn=bin,' + SUFFIX)
t.ok(entry.attributes) t.ok(entry.attributes)
t.ok(entry.attributes.length) t.ok(entry.attributes.length)
t.equal(entry.attributes[0].type, 'foo;binary') 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'), t.equal(entry.attributes[0].buffers[0].toString('base64'),
expect.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) 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]) } for (let i = 0; i < expect2.length; i++) { t.equal(expect2[i], entry.attributes[1].buffers[0][i]) }
t.ok(entry.object)
gotEntry++ gotEntry++
}) })
res.on('error', function (err) { res.on('error', function (err) {
@ -1252,7 +1273,7 @@ tap.test('GH-21 binary attributes', function (t) {
}) })
res.on('end', function (res) { res.on('end', function (res) {
t.ok(res) t.ok(res)
t.ok(res instanceof ldap.SearchResponse) t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0) t.equal(res.status, 0)
t.equal(gotEntry, 1) t.equal(gotEntry, 1)
t.end() t.end()
@ -1271,12 +1292,11 @@ tap.test('GH-23 case insensitive attribute filtering', function (t) {
let gotEntry = 0 let gotEntry = 0
res.on('searchEntry', function (entry) { res.on('searchEntry', function (entry) {
t.ok(entry) t.ok(entry)
t.ok(entry instanceof ldap.SearchEntry) t.ok(entry instanceof SearchResultEntry)
t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX) t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
t.ok(entry.attributes) t.ok(entry.attributes)
t.ok(entry.attributes.length) t.ok(entry.attributes.length)
t.equal(entry.attributes[0].type, 'cn') t.equal(entry.attributes[0].type, 'cn')
t.ok(entry.object)
gotEntry++ gotEntry++
}) })
res.on('error', function (err) { res.on('error', function (err) {
@ -1284,7 +1304,7 @@ tap.test('GH-23 case insensitive attribute filtering', function (t) {
}) })
res.on('end', function (res) { res.on('end', function (res) {
t.ok(res) t.ok(res)
t.ok(res instanceof ldap.SearchResponse) t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0) t.equal(res.status, 0)
t.equal(gotEntry, 2) t.equal(gotEntry, 2)
t.end() t.end()
@ -1303,13 +1323,12 @@ tap.test('GH-24 attribute selection of *', function (t) {
let gotEntry = 0 let gotEntry = 0
res.on('searchEntry', function (entry) { res.on('searchEntry', function (entry) {
t.ok(entry) t.ok(entry)
t.ok(entry instanceof ldap.SearchEntry) t.ok(entry instanceof SearchResultEntry)
t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX) t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
t.ok(entry.attributes) t.ok(entry.attributes)
t.ok(entry.attributes.length) t.ok(entry.attributes.length)
t.equal(entry.attributes[0].type, 'cn') t.equal(entry.attributes[0].type, 'cn')
t.equal(entry.attributes[1].type, 'SN') t.equal(entry.attributes[1].type, 'SN')
t.ok(entry.object)
gotEntry++ gotEntry++
}) })
res.on('error', function (err) { res.on('error', function (err) {
@ -1317,7 +1336,7 @@ tap.test('GH-24 attribute selection of *', function (t) {
}) })
res.on('end', function (res) { res.on('end', function (res) {
t.ok(res) t.ok(res)
t.ok(res instanceof ldap.SearchResponse) t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0) t.equal(res.status, 0)
t.equal(gotEntry, 2) t.equal(gotEntry, 2)
t.end() 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) { t.test('connectTimeout', function (t) {
getPort().then(function (unusedPortNumber) { getPort().then(function (unusedPortNumber) {
const client = ldap.createClient({ const client = ldap.createClient({

View File

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