Compare commits
No commits in common. "master" and "v2.3.2" have entirely different histories.
|
@ -4,9 +4,6 @@ module.exports = {
|
||||||
es2021: true,
|
es2021: true,
|
||||||
node: true
|
node: true
|
||||||
},
|
},
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 'latest'
|
|
||||||
},
|
|
||||||
extends: [
|
extends: [
|
||||||
'standard'
|
'standard'
|
||||||
],
|
],
|
||||||
|
|
|
@ -10,10 +10,10 @@ jobs:
|
||||||
name: Update Docs
|
name: Update Docs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v2.5.1
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: '14'
|
||||||
- name: Install Packages
|
- name: Install Packages
|
||||||
run: npm install
|
run: npm install
|
||||||
- name: Build Docs
|
- name: Build Docs
|
||||||
|
|
|
@ -4,34 +4,35 @@ 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: ghcr.io/ldapjs/docker-test-openldap/openldap:2023-10-30
|
# image: docker.pkg.github.com/ldapjs/docker-test-openldap/openldap:1.0
|
||||||
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@v3
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v2.5.1
|
||||||
with:
|
|
||||||
node-version: 'lts/*'
|
# Hack way to start service since GitHub doesn't integrate with its own services
|
||||||
|
- name: Docker login
|
||||||
|
run: docker login docker.pkg.github.com -u ${GITHUB_ACTOR} -p ${GITHUB_TOKEN}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
- name: Pull Docker image
|
||||||
|
run: docker pull "docker.pkg.github.com/ldapjs/docker-test-openldap/openldap:latest"
|
||||||
|
- name: Start OpenLDAP service
|
||||||
|
run: docker run -it -d --name openldap -p 389:389 -p 636:636 docker.pkg.github.com/ldapjs/docker-test-openldap/openldap:latest
|
||||||
|
|
||||||
- name: Install Packages
|
- name: Install Packages
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
|
@ -4,21 +4,17 @@ 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@v3
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v2.5.1
|
||||||
with:
|
|
||||||
node-version: 'lts/*'
|
|
||||||
- name: Install Packages
|
- name: Install Packages
|
||||||
run: npm install
|
run: npm install
|
||||||
- name: Lint Code
|
- name: Lint Code
|
||||||
|
@ -32,16 +28,27 @@ jobs:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
- windows-latest
|
- windows-latest
|
||||||
node:
|
node:
|
||||||
- 16
|
- 10.13.0
|
||||||
- 18
|
- 10.x
|
||||||
- 20
|
- 12.x
|
||||||
|
- 14.x
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v2.5.1
|
||||||
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
|
||||||
|
|
10
.taprc.yml
10
.taprc.yml
|
@ -1,10 +0,0 @@
|
||||||
# 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'
|
|
74
README.md
74
README.md
|
@ -1,34 +1,54 @@
|
||||||
# Project Decomissioned
|
# LDAPjs
|
||||||
|
|
||||||
This project has been decomissioned. I, James Sumners, took it on when it was
|
[](https://github.com/ldapjs/node-ldapjs/actions)
|
||||||
languishing without any maintenance as it filled a need in the ecosystem and
|
[](https://coveralls.io/github/ldapjs/node-ldapjs/)
|
||||||
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.
|
|
||||||
|
|
||||||
So, why am I just now deciding to decomission this project? Because today,
|
LDAPjs makes the LDAP protocol a first class citizen in Node.js.
|
||||||
2024-05-14, I received the following email:
|
|
||||||
|
|
||||||

|
## Usage
|
||||||
|
|
||||||
I will not tolerate abuse, and I especially will not tolerate tacit death
|
For full docs, head on over to <http://ldapjs.org>.
|
||||||
threats, over a hobby. You can thank the author of that email for the
|
|
||||||
decomissioning on this project.
|
|
||||||
|
|
||||||
My recommendation to you in regard to LDAP operations: write a gateway in a
|
```javascript
|
||||||
language that is more suited to these types of operations. I'd suggest
|
var ldap = require('ldapjs');
|
||||||
[Go](https://go.dev).
|
|
||||||
|
|
||||||
👋
|
var server = ldap.createServer();
|
||||||
|
|
||||||
P.S.: if I ever do need this project again, I might revive it. But I'd fight
|
server.search('dc=example', function(req, res, next) {
|
||||||
hard for my suggestion above. Also, I will consider turning it over to an
|
var obj = {
|
||||||
interested party, but I will require at least one recommendation from a
|
dn: req.dn.toString(),
|
||||||
Node.js core contributor that I can vet with the people that I know on that
|
attributes: {
|
||||||
team.
|
objectclass: ['organization', 'top'],
|
||||||
|
o: 'example'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (req.filter.matches(obj.attributes))
|
||||||
|
res.send(obj);
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(1389, function() {
|
||||||
|
console.log('ldapjs listening at ' + server.url);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
To run that, assuming you've got the [OpenLDAP](http://www.openldap.org/)
|
||||||
|
client on your system:
|
||||||
|
|
||||||
|
ldapsearch -H ldap://localhost:1389 -x -b dc=example objectclass=*
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
npm install ldapjs
|
||||||
|
|
||||||
|
DTrace support is included in ldapjs. To enable it, `npm install dtrace-provider`.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT.
|
||||||
|
|
||||||
|
## Bugs
|
||||||
|
|
||||||
|
See <https://github.com/ldapjs/node-ldapjs/issues>.
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
|
version: '3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
openldap:
|
openldap:
|
||||||
image: ghcr.io/ldapjs/docker-test-openldap/openldap:2023-10-30
|
image: docker.pkg.github.com/ldapjs/docker-test-openldap/openldap:latest
|
||||||
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
|
|
||||||
|
|
|
@ -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('connectError', (err) => {
|
client.on('error', (err) => {
|
||||||
// handle connection error
|
// handle connection error
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
@ -41,6 +41,7 @@ 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
|
||||||
|
@ -213,8 +214,7 @@ Example:
|
||||||
const change = new ldap.Change({
|
const change = new ldap.Change({
|
||||||
operation: 'add',
|
operation: 'add',
|
||||||
modification: {
|
modification: {
|
||||||
type: 'pets',
|
pets: ['cat', 'dog']
|
||||||
values: ['cat', 'dog']
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -235,13 +235,7 @@ 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 required type and values you want.
|
`modification` is just a plain old JS object with the 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)`
|
||||||
|
@ -293,7 +287,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
|
||||||
|
@ -312,10 +306,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.pojo));
|
console.log('entry: ' + JSON.stringify(entry.object));
|
||||||
});
|
});
|
||||||
res.on('searchReference', (referral) => {
|
res.on('searchReference', (referral) => {
|
||||||
console.log('referral: ' + referral.uris.join());
|
console.log('referral: ' + referral.uris.join());
|
||||||
|
|
|
@ -80,15 +80,8 @@ 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 the IPv4 address
|
omitted, the server will accept connections directed to any IPv4 address
|
||||||
`127.0.0.1`. To listen on any other address, supply said address as the `host`
|
(INADDR\_ANY).
|
||||||
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.
|
||||||
|
@ -204,7 +197,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
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
#!/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;
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
// 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'
|
||||||
|
}
|
|
@ -0,0 +1,213 @@
|
||||||
|
// 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
|
|
@ -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('@ldapjs/attribute')
|
const Attribute = require('../attribute')
|
||||||
const Change = require('@ldapjs/change')
|
const Change = require('../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('@ldapjs/protocol')
|
const Protocol = require('../protocol')
|
||||||
const { DN } = require('@ldapjs/dn')
|
const dn = require('../dn')
|
||||||
const errors = require('../errors')
|
const errors = require('../errors')
|
||||||
const filters = require('@ldapjs/filter')
|
const filters = require('../filters')
|
||||||
const Parser = require('../messages/parser')
|
const messages = require('../messages')
|
||||||
const url = require('../url')
|
const url = require('../url')
|
||||||
const CorkedEmitter = require('../corked_emitter')
|
const CorkedEmitter = require('../corked_emitter')
|
||||||
|
|
||||||
/// --- Globals
|
/// --- Globals
|
||||||
|
|
||||||
const messages = require('@ldapjs/messages')
|
const AbandonRequest = messages.AbandonRequest
|
||||||
const {
|
const AddRequest = messages.AddRequest
|
||||||
AbandonRequest,
|
const BindRequest = messages.BindRequest
|
||||||
AddRequest,
|
const CompareRequest = messages.CompareRequest
|
||||||
BindRequest,
|
const DeleteRequest = messages.DeleteRequest
|
||||||
CompareRequest,
|
const ExtendedRequest = messages.ExtendedRequest
|
||||||
DeleteRequest,
|
const ModifyRequest = messages.ModifyRequest
|
||||||
ExtensionRequest: ExtendedRequest,
|
const ModifyDNRequest = messages.ModifyDNRequest
|
||||||
ModifyRequest,
|
const SearchRequest = messages.SearchRequest
|
||||||
ModifyDnRequest: ModifyDNRequest,
|
const UnbindRequest = messages.UnbindRequest
|
||||||
SearchRequest,
|
const UnbindResponse = messages.UnbindResponse
|
||||||
UnbindRequest,
|
|
||||||
LdapResult: LDAPResult,
|
const LDAPResult = messages.LDAPResult
|
||||||
SearchResultEntry: SearchEntry,
|
const SearchEntry = messages.SearchEntry
|
||||||
SearchResultReference: SearchReference
|
const SearchReference = messages.SearchReference
|
||||||
} = messages
|
// var SearchResponse = messages.SearchResponse
|
||||||
|
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) && !(c instanceof LdapControl)) { throw new TypeError('controls must be [Control]') }
|
if (!(c instanceof Control)) { throw new TypeError('controls must be [Control]') }
|
||||||
})
|
})
|
||||||
} else if (controls instanceof Control || controls instanceof LdapControl) {
|
} else if (controls instanceof Control) {
|
||||||
controls = [controls]
|
controls = [controls]
|
||||||
} else {
|
} else {
|
||||||
throw new TypeError('controls must be [Control]')
|
throw new TypeError('controls must be [Control]')
|
||||||
|
@ -78,11 +78,13 @@ function validateControls (controls) {
|
||||||
return controls
|
return controls
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureDN (input) {
|
function ensureDN (input, strict) {
|
||||||
if (DN.isDn(input)) {
|
if (dn.DN.isDN(input)) {
|
||||||
return input
|
return dn
|
||||||
|
} else if (strict) {
|
||||||
|
return dn.parse(input)
|
||||||
} else if (typeof (input) === 'string') {
|
} else if (typeof (input) === 'string') {
|
||||||
return DN.fromString(input)
|
return input
|
||||||
} else {
|
} else {
|
||||||
throw new Error('invalid DN')
|
throw new Error('invalid DN')
|
||||||
}
|
}
|
||||||
|
@ -134,6 +136,7 @@ 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),
|
||||||
|
@ -174,13 +177,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 = []
|
||||||
|
@ -190,8 +193,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)
|
||||||
|
@ -245,9 +248,9 @@ Client.prototype.add = function add (name, entry, controls, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const req = new AddRequest({
|
const req = new AddRequest({
|
||||||
entry: ensureDN(name),
|
entry: ensureDN(name, this.strictDN),
|
||||||
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)
|
||||||
|
@ -267,12 +270,7 @@ Client.prototype.bind = function bind (name,
|
||||||
controls,
|
controls,
|
||||||
callback,
|
callback,
|
||||||
_bypass) {
|
_bypass) {
|
||||||
if (
|
if (typeof (name) !== 'string' && !(name instanceof dn.DN)) { throw new TypeError('name (string) required') }
|
||||||
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
|
||||||
|
@ -286,7 +284,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)
|
||||||
|
@ -327,10 +325,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),
|
entry: ensureDN(name, this.strictDN),
|
||||||
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) {
|
||||||
|
@ -359,8 +357,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),
|
entry: ensureDN(name, this.strictDN),
|
||||||
controls
|
controls: controls
|
||||||
})
|
})
|
||||||
|
|
||||||
return this._send(req, [errors.LDAP_SUCCESS], null, callback)
|
return this._send(req, [errors.LDAP_SUCCESS], null, callback)
|
||||||
|
@ -397,7 +395,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) {
|
||||||
|
@ -470,9 +468,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),
|
object: ensureDN(name, this.strictDN),
|
||||||
changes,
|
changes: changes,
|
||||||
controls
|
controls: controls
|
||||||
})
|
})
|
||||||
|
|
||||||
return this._send(req, [errors.LDAP_SUCCESS], null, callback)
|
return this._send(req, [errors.LDAP_SUCCESS], null, callback)
|
||||||
|
@ -506,16 +504,18 @@ Client.prototype.modifyDN = function modifyDN (name,
|
||||||
}
|
}
|
||||||
assert.func(callback)
|
assert.func(callback)
|
||||||
|
|
||||||
const newDN = DN.fromString(newName)
|
const DN = ensureDN(name)
|
||||||
|
// TODO: is non-strict handling desired here?
|
||||||
|
const newDN = dn.parse(newName)
|
||||||
|
|
||||||
const req = new ModifyDNRequest({
|
const req = new ModifyDNRequest({
|
||||||
entry: DN.fromString(name),
|
entry: DN,
|
||||||
deleteOldRdn: true,
|
deleteOldRdn: true,
|
||||||
controls
|
controls: controls
|
||||||
})
|
})
|
||||||
|
|
||||||
if (newDN.length !== 1) {
|
if (newDN.length !== 1) {
|
||||||
req.newRdn = DN.fromString(newDN.shift().toString())
|
req.newRdn = dn.parse(newDN.rdns.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 (Object.prototype.toString.call(options.filter) !== '[object FilterString]') {
|
} else if (!filters.isFilter(options.filter)) {
|
||||||
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)
|
const baseDN = ensureDN(base, this.strictDN)
|
||||||
|
|
||||||
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.search.NEVER_DEREF_ALIASES,
|
derefAliases: options.derefAliases || Protocol.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 }, 'error event: %s', new Error().stack)
|
self.log.trace({ err: 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 })
|
parser: new Parser({ log: log })
|
||||||
})
|
})
|
||||||
|
|
||||||
// This won't be set on TLS. So. Very. Annoying.
|
// This won't be set on TLS. So. Very. Annoying.
|
||||||
|
@ -876,52 +876,15 @@ 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 trackedObject = tracker.fetch(message.messageId)
|
const callback = 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.pojo }, 'unsolicited message')
|
log.error({ message: message.json }, '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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1002,7 +965,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 }, 'error event: %s', new Error().stack)
|
log.trace({ err: err }, 'error event: %s', new Error().stack)
|
||||||
|
|
||||||
self.emit('error', err)
|
self.emit('error', err)
|
||||||
socket.destroy()
|
socket.destroy()
|
||||||
|
@ -1120,16 +1083,8 @@ 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
|
||||||
// TODO: we are faking this "UnbindResponse" object in order to make
|
const unbind = new UnbindResponse({ messageID: msgid })
|
||||||
// tests pass. There is no such thing as an "unbind response" in the LDAP
|
unbind.status = 'unbind'
|
||||||
// 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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1247,23 +1202,21 @@ 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.pojo : null }, 'response received')
|
log.trace({ msg: msg ? msg.json : 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
|
||||||
// Generate the event name for the event emitter, i.e. "searchEntry"
|
event = event[0].toLowerCase() + event.slice(1)
|
||||||
// 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 (msg.status !== 0 && expect.indexOf(msg.status) === -1) {
|
if (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)
|
||||||
|
@ -1277,7 +1230,7 @@ Client.prototype._sendSocket = function _sendSocket (message,
|
||||||
|
|
||||||
function onRequestTimeout () {
|
function onRequestTimeout () {
|
||||||
self.emit('timeout', message)
|
self.emit('timeout', message)
|
||||||
const { callback: cb } = tracker.fetch(message.messageId)
|
const 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)'))
|
||||||
|
@ -1286,8 +1239,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)
|
||||||
|
@ -1319,11 +1272,10 @@ Client.prototype._sendSocket = function _sendSocket (message,
|
||||||
timer = setTimeout(onRequestTimeout, self.timeout)
|
timer = setTimeout(onRequestTimeout, self.timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.trace('sending request %j', message.pojo)
|
log.trace('sending request %j', message.json)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const messageBer = message.toBer()
|
return conn.write(message.toBer(), writeCallback)
|
||||||
return conn.write(messageBer.buffer, writeCallback)
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (timer) { clearTimeout(timer) }
|
if (timer) { clearTimeout(timer) }
|
||||||
|
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -62,23 +62,13 @@ 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,
|
||||||
message: toAbandon.message,
|
cb: messages.get(msgID)
|
||||||
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.
|
||||||
|
@ -89,10 +79,10 @@ module.exports = function messageTrackerFactory (options) {
|
||||||
* @method fetch
|
* @method fetch
|
||||||
*/
|
*/
|
||||||
tracker.fetch = function fetchMessage (msgID) {
|
tracker.fetch = function fetchMessage (msgID) {
|
||||||
const tracked = messages.get(msgID)
|
const messageCB = messages.get(msgID)
|
||||||
if (tracked) {
|
if (messageCB) {
|
||||||
purgeAbandoned(msgID, abandoned)
|
purgeAbandoned(msgID, abandoned)
|
||||||
return tracked
|
return messageCB
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -101,7 +91,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 { message: abandonedMsg, callback: abandonedMsg.cb }
|
return abandonedMsg.cb
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
@ -120,7 +110,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.callback)
|
cb(key, val)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +132,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
|
||||||
|
@ -153,8 +143,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, message })
|
messages.set(currentID, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tracker
|
return tracker
|
||||||
|
|
|
@ -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.size >= this.size || this._frozen) {
|
if (this._queue.length >= this.size || this._frozen) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,14 @@
|
||||||
|
|
||||||
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
|
||||||
|
@ -41,7 +47,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 === PagedResultsControl.OID) {
|
if (control.type === PagedControl.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')
|
||||||
|
@ -73,7 +79,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 === PagedResultsControl.OID) {
|
if (control.type === PagedControl.OID) {
|
||||||
cookie = control.value.cookie
|
cookie = control.value.cookie
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -89,13 +95,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 delivering the fault via the 'error' event. Emitting an 'end'
|
// from deliverying 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 explanation above.
|
// No end event possible per explaination above.
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -134,10 +140,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 PagedResultsControl({
|
controls.push(new PagedControl({
|
||||||
value: {
|
value: {
|
||||||
size: this.pageSize,
|
size: this.pageSize,
|
||||||
cookie
|
cookie: cookie
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,83 @@
|
||||||
|
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
|
|
@ -1,4 +1,86 @@
|
||||||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||||
|
|
||||||
const controls = require('@ldapjs/controls')
|
const assert = require('assert')
|
||||||
module.exports = controls
|
const Ber = require('asn1').Ber
|
||||||
|
|
||||||
|
const Control = require('./control')
|
||||||
|
const EntryChangeNotificationControl =
|
||||||
|
require('./entry_change_notification_control')
|
||||||
|
const PersistentSearchControl = require('./persistent_search_control')
|
||||||
|
const PagedResultsControl = require('./paged_results_control')
|
||||||
|
const ServerSideSortingRequestControl =
|
||||||
|
require('./server_side_sorting_request_control.js')
|
||||||
|
const ServerSideSortingResponseControl =
|
||||||
|
require('./server_side_sorting_response_control.js')
|
||||||
|
const VirtualListViewRequestControl =
|
||||||
|
require('./virtual_list_view_request_control.js')
|
||||||
|
const VirtualListViewResponseControl =
|
||||||
|
require('./virtual_list_view_response_control.js')
|
||||||
|
|
||||||
|
/// --- API
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
getControl: function getControl (ber) {
|
||||||
|
assert.ok(ber)
|
||||||
|
|
||||||
|
if (ber.readSequence() === null) { return null }
|
||||||
|
|
||||||
|
let type
|
||||||
|
const opts = {
|
||||||
|
criticality: false,
|
||||||
|
value: null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ber.length) {
|
||||||
|
const end = ber.offset + ber.length
|
||||||
|
|
||||||
|
type = ber.readString()
|
||||||
|
if (ber.offset < end) {
|
||||||
|
if (ber.peek() === Ber.Boolean) { opts.criticality = ber.readBoolean() }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ber.offset < end) { opts.value = ber.readString(Ber.OctetString, true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
let control
|
||||||
|
switch (type) {
|
||||||
|
case PersistentSearchControl.OID:
|
||||||
|
control = new PersistentSearchControl(opts)
|
||||||
|
break
|
||||||
|
case EntryChangeNotificationControl.OID:
|
||||||
|
control = new EntryChangeNotificationControl(opts)
|
||||||
|
break
|
||||||
|
case PagedResultsControl.OID:
|
||||||
|
control = new PagedResultsControl(opts)
|
||||||
|
break
|
||||||
|
case ServerSideSortingRequestControl.OID:
|
||||||
|
control = new ServerSideSortingRequestControl(opts)
|
||||||
|
break
|
||||||
|
case ServerSideSortingResponseControl.OID:
|
||||||
|
control = new ServerSideSortingResponseControl(opts)
|
||||||
|
break
|
||||||
|
case VirtualListViewRequestControl.OID:
|
||||||
|
control = new VirtualListViewRequestControl(opts)
|
||||||
|
break
|
||||||
|
case VirtualListViewResponseControl.OID:
|
||||||
|
control = new VirtualListViewResponseControl(opts)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
opts.type = type
|
||||||
|
control = new Control(opts)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return control
|
||||||
|
},
|
||||||
|
|
||||||
|
Control: Control,
|
||||||
|
EntryChangeNotificationControl: EntryChangeNotificationControl,
|
||||||
|
PagedResultsControl: PagedResultsControl,
|
||||||
|
PersistentSearchControl: PersistentSearchControl,
|
||||||
|
ServerSideSortingRequestControl: ServerSideSortingRequestControl,
|
||||||
|
ServerSideSortingResponseControl: ServerSideSortingResponseControl,
|
||||||
|
VirtualListViewRequestControl: VirtualListViewRequestControl,
|
||||||
|
VirtualListViewResponseControl: VirtualListViewResponseControl
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
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
|
|
@ -0,0 +1,82 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,108 @@
|
||||||
|
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
|
|
@ -0,0 +1,100 @@
|
||||||
|
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
|
|
@ -0,0 +1,94 @@
|
||||||
|
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
|
|
@ -0,0 +1,112 @@
|
||||||
|
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
|
|
@ -0,0 +1,473 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
// 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
|
||||||
|
}())
|
|
@ -86,7 +86,7 @@ Object.keys(CODES).forEach(function (code) {
|
||||||
})
|
})
|
||||||
|
|
||||||
ERRORS[CODES[code]] = {
|
ERRORS[CODES[code]] = {
|
||||||
err,
|
err: err,
|
||||||
message: msg
|
message: msg
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,208 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// 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)
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
// 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
|
||||||
|
}
|
26
lib/index.js
26
lib/index.js
|
@ -3,16 +3,16 @@
|
||||||
const logger = require('./logger')
|
const logger = require('./logger')
|
||||||
|
|
||||||
const client = require('./client')
|
const client = require('./client')
|
||||||
const Attribute = require('@ldapjs/attribute')
|
const Attribute = require('./attribute')
|
||||||
const Change = require('@ldapjs/change')
|
const Change = require('./change')
|
||||||
const Protocol = require('@ldapjs/protocol')
|
const Protocol = require('./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('@ldapjs/dn')
|
const dn = require('./dn')
|
||||||
const errors = require('./errors')
|
const errors = require('./errors')
|
||||||
const filters = require('@ldapjs/filter')
|
const filters = require('./filters')
|
||||||
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.DN.fromString,
|
parseDN: dn.parse,
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,34 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,159 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,22 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,84 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,22 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,74 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,33 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,62 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,22 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,117 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,86 @@
|
||||||
|
// 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
|
|
@ -1,39 +1,61 @@
|
||||||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||||
|
|
||||||
const messages = require('@ldapjs/messages')
|
const LDAPMessage = require('./message')
|
||||||
|
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: messages.LdapMessage,
|
LDAPMessage: LDAPMessage,
|
||||||
LDAPResult: messages.LdapResult,
|
LDAPResult: LDAPResult,
|
||||||
Parser,
|
Parser: Parser,
|
||||||
|
|
||||||
AbandonRequest: messages.AbandonRequest,
|
AbandonRequest: AbandonRequest,
|
||||||
AbandonResponse: messages.AbandonResponse,
|
AbandonResponse: AbandonResponse,
|
||||||
AddRequest: messages.AddRequest,
|
AddRequest: AddRequest,
|
||||||
AddResponse: messages.AddResponse,
|
AddResponse: AddResponse,
|
||||||
BindRequest: messages.BindRequest,
|
BindRequest: BindRequest,
|
||||||
BindResponse: messages.BindResponse,
|
BindResponse: BindResponse,
|
||||||
CompareRequest: messages.CompareRequest,
|
CompareRequest: CompareRequest,
|
||||||
CompareResponse: messages.CompareResponse,
|
CompareResponse: CompareResponse,
|
||||||
DeleteRequest: messages.DeleteRequest,
|
DeleteRequest: DeleteRequest,
|
||||||
DeleteResponse: messages.DeleteResponse,
|
DeleteResponse: DeleteResponse,
|
||||||
ExtendedRequest: messages.ExtensionRequest,
|
ExtendedRequest: ExtendedRequest,
|
||||||
ExtendedResponse: messages.ExtensionResponse,
|
ExtendedResponse: ExtendedResponse,
|
||||||
ModifyRequest: messages.ModifyRequest,
|
ModifyRequest: ModifyRequest,
|
||||||
ModifyResponse: messages.ModifyResponse,
|
ModifyResponse: ModifyResponse,
|
||||||
ModifyDNRequest: messages.ModifyDnRequest,
|
ModifyDNRequest: ModifyDNRequest,
|
||||||
ModifyDNResponse: messages.ModifyDnResponse,
|
ModifyDNResponse: ModifyDNResponse,
|
||||||
SearchRequest: messages.SearchRequest,
|
SearchRequest: SearchRequest,
|
||||||
SearchEntry: messages.SearchResultEntry,
|
SearchEntry: SearchEntry,
|
||||||
SearchReference: messages.SearchResultReference,
|
SearchReference: SearchReference,
|
||||||
SearchResponse,
|
SearchResponse: SearchResponse,
|
||||||
UnbindRequest: messages.UnbindRequest
|
UnbindRequest: UnbindRequest,
|
||||||
|
UnbindResponse: UnbindResponse
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,85 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,22 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,83 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,22 @@
|
||||||
|
// 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
|
|
@ -4,36 +4,40 @@ 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('@ldapjs/asn1')
|
const asn1 = require('asn1')
|
||||||
|
// var VError = require('verror').VError
|
||||||
const logger = require('../logger')
|
const logger = require('../logger')
|
||||||
|
|
||||||
const messages = require('@ldapjs/messages')
|
const AbandonRequest = require('./abandon_request')
|
||||||
const AbandonRequest = messages.AbandonRequest
|
const AddRequest = require('./add_request')
|
||||||
const AddRequest = messages.AddRequest
|
const AddResponse = require('./add_response')
|
||||||
const AddResponse = messages.AddResponse
|
const BindRequest = require('./bind_request')
|
||||||
const BindRequest = messages.BindRequest
|
const BindResponse = require('./bind_response')
|
||||||
const BindResponse = messages.BindResponse
|
const CompareRequest = require('./compare_request')
|
||||||
const CompareRequest = messages.CompareRequest
|
const CompareResponse = require('./compare_response')
|
||||||
const CompareResponse = messages.CompareResponse
|
const DeleteRequest = require('./del_request')
|
||||||
const DeleteRequest = messages.DeleteRequest
|
const DeleteResponse = require('./del_response')
|
||||||
const DeleteResponse = messages.DeleteResponse
|
const ExtendedRequest = require('./ext_request')
|
||||||
const ExtendedRequest = messages.ExtensionRequest
|
const ExtendedResponse = require('./ext_response')
|
||||||
const ExtendedResponse = messages.ExtensionResponse
|
const ModifyRequest = require('./modify_request')
|
||||||
const ModifyRequest = messages.ModifyRequest
|
const ModifyResponse = require('./modify_response')
|
||||||
const ModifyResponse = messages.ModifyResponse
|
const ModifyDNRequest = require('./moddn_request')
|
||||||
const ModifyDNRequest = messages.ModifyDnRequest
|
const ModifyDNResponse = require('./moddn_response')
|
||||||
const ModifyDNResponse = messages.ModifyDnResponse
|
const SearchRequest = require('./search_request')
|
||||||
const SearchRequest = messages.SearchRequest
|
const SearchEntry = require('./search_entry')
|
||||||
const SearchEntry = messages.SearchResultEntry
|
const SearchReference = require('./search_reference')
|
||||||
const SearchReference = messages.SearchResultReference
|
|
||||||
const SearchResponse = require('./search_response')
|
const SearchResponse = require('./search_response')
|
||||||
const UnbindRequest = messages.UnbindRequest
|
const UnbindRequest = require('./unbind_request')
|
||||||
const LDAPResult = messages.LdapResult
|
// var UnbindResponse = require('./unbind_response')
|
||||||
|
|
||||||
const Protocol = require('@ldapjs/protocol')
|
const LDAPResult = require('./result')
|
||||||
|
// var Message = require('./message')
|
||||||
|
|
||||||
|
const Protocol = require('../protocol')
|
||||||
|
|
||||||
/// --- Globals
|
/// --- Globals
|
||||||
|
|
||||||
|
// var Ber = asn1.Ber
|
||||||
const BerReader = asn1.BerReader
|
const BerReader = asn1.BerReader
|
||||||
|
|
||||||
/// --- API
|
/// --- API
|
||||||
|
@ -48,13 +52,6 @@ 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') }
|
||||||
|
|
||||||
|
@ -67,9 +64,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)
|
||||||
|
|
||||||
let ber = new BerReader(self.buffer)
|
const ber = new BerReader(self.buffer)
|
||||||
|
|
||||||
let foundSeq = false
|
let foundSeq = false
|
||||||
try {
|
try {
|
||||||
|
@ -83,22 +80,9 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,25 +92,13 @@ 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
|
||||||
|
@ -141,88 +113,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.operations.LDAP_REQ_ABANDON:
|
case Protocol.LDAP_REQ_ABANDON:
|
||||||
Message = AbandonRequest
|
Message = AbandonRequest
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_REQ_ADD:
|
case Protocol.LDAP_REQ_ADD:
|
||||||
Message = AddRequest
|
Message = AddRequest
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_RES_ADD:
|
case Protocol.LDAP_REP_ADD:
|
||||||
Message = AddResponse
|
Message = AddResponse
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_REQ_BIND:
|
case Protocol.LDAP_REQ_BIND:
|
||||||
Message = BindRequest
|
Message = BindRequest
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_RES_BIND:
|
case Protocol.LDAP_REP_BIND:
|
||||||
Message = BindResponse
|
Message = BindResponse
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_REQ_COMPARE:
|
case Protocol.LDAP_REQ_COMPARE:
|
||||||
Message = CompareRequest
|
Message = CompareRequest
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_RES_COMPARE:
|
case Protocol.LDAP_REP_COMPARE:
|
||||||
Message = CompareResponse
|
Message = CompareResponse
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_REQ_DELETE:
|
case Protocol.LDAP_REQ_DELETE:
|
||||||
Message = DeleteRequest
|
Message = DeleteRequest
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_RES_DELETE:
|
case Protocol.LDAP_REP_DELETE:
|
||||||
Message = DeleteResponse
|
Message = DeleteResponse
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_REQ_EXTENSION:
|
case Protocol.LDAP_REQ_EXTENSION:
|
||||||
Message = ExtendedRequest
|
Message = ExtendedRequest
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_RES_EXTENSION:
|
case Protocol.LDAP_REP_EXTENSION:
|
||||||
Message = ExtendedResponse
|
Message = ExtendedResponse
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_REQ_MODIFY:
|
case Protocol.LDAP_REQ_MODIFY:
|
||||||
Message = ModifyRequest
|
Message = ModifyRequest
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_RES_MODIFY:
|
case Protocol.LDAP_REP_MODIFY:
|
||||||
Message = ModifyResponse
|
Message = ModifyResponse
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_REQ_MODRDN:
|
case Protocol.LDAP_REQ_MODRDN:
|
||||||
Message = ModifyDNRequest
|
Message = ModifyDNRequest
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_RES_MODRDN:
|
case Protocol.LDAP_REP_MODRDN:
|
||||||
Message = ModifyDNResponse
|
Message = ModifyDNResponse
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_REQ_SEARCH:
|
case Protocol.LDAP_REQ_SEARCH:
|
||||||
Message = SearchRequest
|
Message = SearchRequest
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_RES_SEARCH_ENTRY:
|
case Protocol.LDAP_REP_SEARCH_ENTRY:
|
||||||
Message = SearchEntry
|
Message = SearchEntry
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_RES_SEARCH_REF:
|
case Protocol.LDAP_REP_SEARCH_REF:
|
||||||
Message = SearchReference
|
Message = SearchReference
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_RES_SEARCH:
|
case Protocol.LDAP_REP_SEARCH:
|
||||||
Message = SearchResponse
|
Message = SearchResponse
|
||||||
break
|
break
|
||||||
|
|
||||||
case Protocol.operations.LDAP_REQ_UNBIND:
|
case Protocol.LDAP_REQ_UNBIND:
|
||||||
Message = UnbindRequest
|
Message = UnbindRequest
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -231,15 +203,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.operations.LDAP_RES_EXTENSION
|
protocolOp: type || Protocol.LDAP_REP_EXTENSION
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Message({
|
return new Message({
|
||||||
messageId,
|
messageID: messageID,
|
||||||
log: self.log
|
log: self.log
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,188 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,101 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,152 @@
|
||||||
|
// 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
|
|
@ -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 Attribute = require('@ldapjs/attribute')
|
const LDAPResult = require('./result')
|
||||||
const {
|
const SearchEntry = require('./search_entry')
|
||||||
SearchResultEntry: SearchEntry,
|
const SearchReference = require('./search_reference')
|
||||||
SearchResultReference: SearchReference,
|
|
||||||
SearchResultDone
|
|
||||||
} = require('@ldapjs/messages')
|
|
||||||
|
|
||||||
const parseDN = require('@ldapjs/dn').DN.fromString
|
const dtrace = require('../dtrace')
|
||||||
|
const parseDN = require('../dn').parse
|
||||||
|
const parseURL = require('../url').parse
|
||||||
|
const Protocol = require('../protocol')
|
||||||
|
|
||||||
/// --- API
|
/// --- API
|
||||||
|
|
||||||
class SearchResponse extends SearchResultDone {
|
function SearchResponse (options) {
|
||||||
attributes
|
options = options || {}
|
||||||
notAttributes
|
assert.object(options)
|
||||||
sentEntries
|
|
||||||
|
|
||||||
constructor (options = {}) {
|
options.protocolOp = Protocol.LDAP_REP_SEARCH
|
||||||
super(options)
|
LDAPResult.call(this, 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,16 +44,12 @@ 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) {
|
if (entry.messageID !== this.messageID) { throw new Error('SearchEntry messageID mismatch') }
|
||||||
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] === '_') {
|
||||||
|
@ -73,24 +69,39 @@ 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,
|
||||||
attributes: Attribute.fromObject(entry.attributes)
|
log: self.log
|
||||||
})
|
})
|
||||||
|
entry.fromObject(save)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.log.debug('%s: sending: %j', this.connection.ldap.id, entry.pojo)
|
this.log.debug('%s: sending: %j', this.connection.ldap.id, entry.json)
|
||||||
|
|
||||||
this.connection.write(entry.toBer().buffer)
|
this.connection.write(entry.toBer())
|
||||||
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.pojo)
|
this.connection.ldap.id, this.json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,10 +109,11 @@ 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,
|
||||||
objectName: object.objectName || object.dn,
|
log: this.log,
|
||||||
attributes: object.attributes ?? []
|
objectName: object.objectName || object.dn
|
||||||
})
|
})
|
||||||
|
entry.fromObject((object.attributes || object))
|
||||||
return entry
|
return entry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,10 +122,15 @@ 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,
|
||||||
uri: uris
|
log: self.log,
|
||||||
|
uris: uris
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
// 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
|
|
@ -0,0 +1,65 @@
|
||||||
|
// 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
|
|
@ -89,7 +89,7 @@ function getEntryChangeNotificationControl (req, obj) {
|
||||||
}
|
}
|
||||||
|
|
||||||
value.changeNumber = attrs.changenumber
|
value.changeNumber = attrs.changenumber
|
||||||
return new EntryChangeNotificationControl({ value })
|
return new EntryChangeNotificationControl({ value: 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
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
// 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
|
||||||
|
}
|
339
lib/server.js
339
lib/server.js
|
@ -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('@ldapjs/asn1')
|
// var asn1 = require('asn1')
|
||||||
const VError = require('verror').VError
|
const VError = require('verror').VError
|
||||||
|
|
||||||
const { DN, RDN } = require('@ldapjs/dn')
|
const dn = require('./dn')
|
||||||
|
const dtrace = require('./dtrace')
|
||||||
const errors = require('./errors')
|
const errors = require('./errors')
|
||||||
const Protocol = require('@ldapjs/protocol')
|
const Protocol = require('./protocol')
|
||||||
|
|
||||||
const messages = require('@ldapjs/messages')
|
|
||||||
|
|
||||||
const Parser = require('./messages').Parser
|
const Parser = require('./messages').Parser
|
||||||
const LdapResult = messages.LdapResult
|
const AbandonResponse = require('./messages/abandon_response')
|
||||||
const AbandonResponse = messages.AbandonResponse
|
const AddResponse = require('./messages/add_response')
|
||||||
const AddResponse = messages.AddResponse
|
const BindResponse = require('./messages/bind_response')
|
||||||
const BindResponse = messages.BindResponse
|
const CompareResponse = require('./messages/compare_response')
|
||||||
const CompareResponse = messages.CompareResponse
|
const DeleteResponse = require('./messages/del_response')
|
||||||
const DeleteResponse = messages.DeleteResponse
|
const ExtendedResponse = require('./messages/ext_response')
|
||||||
const ExtendedResponse = messages.ExtensionResponse
|
// var LDAPResult = require('./messages/result')
|
||||||
const ModifyResponse = messages.ModifyResponse
|
const ModifyResponse = require('./messages/modify_response')
|
||||||
const ModifyDnResponse = messages.ModifyDnResponse
|
const ModifyDNResponse = require('./messages/moddn_response')
|
||||||
const SearchRequest = messages.SearchRequest
|
const SearchRequest = require('./messages/search_request')
|
||||||
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 (Array.isArray(argv[i])) {
|
if (argv[i] instanceof Array) {
|
||||||
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 (typeof arr[j] !== 'function') {
|
if (!(arr[j] instanceof 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 (typeof argv[i] === 'function') {
|
} else if (argv[i] instanceof 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,42 +71,35 @@ function getResponse (req) {
|
||||||
let Response
|
let Response
|
||||||
|
|
||||||
switch (req.protocolOp) {
|
switch (req.protocolOp) {
|
||||||
case Protocol.operations.LDAP_REQ_BIND:
|
case Protocol.LDAP_REQ_BIND:
|
||||||
Response = BindResponse
|
Response = BindResponse
|
||||||
break
|
break
|
||||||
case Protocol.operations.LDAP_REQ_ABANDON:
|
case Protocol.LDAP_REQ_ABANDON:
|
||||||
Response = AbandonResponse
|
Response = AbandonResponse
|
||||||
break
|
break
|
||||||
case Protocol.operations.LDAP_REQ_ADD:
|
case Protocol.LDAP_REQ_ADD:
|
||||||
Response = AddResponse
|
Response = AddResponse
|
||||||
break
|
break
|
||||||
case Protocol.operations.LDAP_REQ_COMPARE:
|
case Protocol.LDAP_REQ_COMPARE:
|
||||||
Response = CompareResponse
|
Response = CompareResponse
|
||||||
break
|
break
|
||||||
case Protocol.operations.LDAP_REQ_DELETE:
|
case Protocol.LDAP_REQ_DELETE:
|
||||||
Response = DeleteResponse
|
Response = DeleteResponse
|
||||||
break
|
break
|
||||||
case Protocol.operations.LDAP_REQ_EXTENSION:
|
case Protocol.LDAP_REQ_EXTENSION:
|
||||||
Response = ExtendedResponse
|
Response = ExtendedResponse
|
||||||
break
|
break
|
||||||
case Protocol.operations.LDAP_REQ_MODIFY:
|
case Protocol.LDAP_REQ_MODIFY:
|
||||||
Response = ModifyResponse
|
Response = ModifyResponse
|
||||||
break
|
break
|
||||||
case Protocol.operations.LDAP_REQ_MODRDN:
|
case Protocol.LDAP_REQ_MODRDN:
|
||||||
Response = ModifyDnResponse
|
Response = ModifyDNResponse
|
||||||
break
|
break
|
||||||
case Protocol.operations.LDAP_REQ_SEARCH:
|
case Protocol.LDAP_REQ_SEARCH:
|
||||||
Response = SearchResponse
|
Response = SearchResponse
|
||||||
break
|
break
|
||||||
case Protocol.operations.LDAP_REQ_UNBIND:
|
case Protocol.LDAP_REQ_UNBIND:
|
||||||
// TODO: when the server receives an unbind request this made up response object was returned.
|
Response = UnbindResponse
|
||||||
// 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
|
||||||
|
@ -114,83 +107,16 @@ 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)
|
||||||
|
@ -231,6 +157,69 @@ 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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -272,6 +261,8 @@ 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) {
|
||||||
|
@ -286,12 +277,12 @@ function Server (options) {
|
||||||
c.remotePort = c.socket.remotePort
|
c.remotePort = c.socket.remotePort
|
||||||
}
|
}
|
||||||
|
|
||||||
const rdn = new RDN({ cn: 'anonymous' })
|
const rdn = new dn.RDN({ cn: 'anonymous' })
|
||||||
|
|
||||||
c.ldap = {
|
c.ldap = {
|
||||||
id: c.remoteAddress + ':' + c.remotePort,
|
id: c.remoteAddress + ':' + c.remotePort,
|
||||||
config: options,
|
config: options,
|
||||||
_bindDN: new DN({ rdns: [rdn] })
|
_bindDN: new DN([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)
|
||||||
|
@ -314,9 +305,7 @@ function Server (options) {
|
||||||
return c.ldap._bindDN
|
return c.ldap._bindDN
|
||||||
})
|
})
|
||||||
c.ldap.__defineSetter__('bindDN', function (val) {
|
c.ldap.__defineSetter__('bindDN', function (val) {
|
||||||
if (Object.prototype.toString.call(val) !== '[object LdapDn]') {
|
if (!(val instanceof DN)) { throw new TypeError('DN required') }
|
||||||
throw new TypeError('DN required')
|
|
||||||
}
|
|
||||||
|
|
||||||
c.ldap._bindDN = val
|
c.ldap._bindDN = val
|
||||||
return val
|
return val
|
||||||
|
@ -330,17 +319,19 @@ 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.pojo)
|
log.debug('%s: message received: req=%j', conn.ldap.id, req.json)
|
||||||
|
|
||||||
const res = getResponse(req)
|
const res = getResponse(req)
|
||||||
if (!res) {
|
if (!res) {
|
||||||
|
@ -352,48 +343,32 @@ 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.operations.LDAP_REQ_BIND: {
|
case Protocol.LDAP_REQ_BIND:
|
||||||
req.name = DN.fromString(req.name)
|
req.name = dn.parse(req.name)
|
||||||
break
|
break
|
||||||
}
|
case Protocol.LDAP_REQ_ADD:
|
||||||
|
case Protocol.LDAP_REQ_COMPARE:
|
||||||
case Protocol.operations.LDAP_REQ_ADD:
|
case Protocol.LDAP_REQ_DELETE:
|
||||||
case Protocol.operations.LDAP_REQ_COMPARE:
|
req.entry = dn.parse(req.entry)
|
||||||
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
|
||||||
|
@ -431,19 +406,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.operations.LDAP_REQ_BIND && res.status === 0) {
|
if (req.protocolOp === Protocol.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({ rdns: [new RDN({ cn: 'anonymous' })] })
|
conn.ldap.bindDN = new DN([new dn.RDN({ cn: 'anonymous' })])
|
||||||
} else {
|
} else {
|
||||||
conn.ldap.bindDN = DN.fromString(req.dn)
|
conn.ldap.bindDN = 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.operations.LDAP_REQ_UNBIND && res.status === 0) {
|
if (req.protocolOp === Protocol.LDAP_REQ_UNBIND && res.status === 0) {
|
||||||
conn.ldap.bindDN = new DN({ rdns: [new RDN({ cn: 'anonymous' })] })
|
conn.ldap.bindDN = new DN([new dn.RDN({ cn: 'anonymous' })])
|
||||||
}
|
}
|
||||||
|
|
||||||
return after()
|
return after()
|
||||||
|
@ -533,15 +508,7 @@ 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
|
||||||
|
@ -561,7 +528,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.operations.LDAP_REQ_ADD, name, args)
|
return this._mount(Protocol.LDAP_REQ_ADD, name, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -576,7 +543,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.operations.LDAP_REQ_BIND, name, args)
|
return this._mount(Protocol.LDAP_REQ_BIND, name, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -591,7 +558,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.operations.LDAP_REQ_COMPARE, name, args)
|
return this._mount(Protocol.LDAP_REQ_COMPARE, name, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -606,7 +573,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.operations.LDAP_REQ_DELETE, name, args)
|
return this._mount(Protocol.LDAP_REQ_DELETE, name, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -621,7 +588,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.operations.LDAP_REQ_EXTENSION, name, args, true)
|
return this._mount(Protocol.LDAP_REQ_EXTENSION, name, args, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -636,7 +603,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.operations.LDAP_REQ_MODIFY, name, args)
|
return this._mount(Protocol.LDAP_REQ_MODIFY, name, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -651,7 +618,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.operations.LDAP_REQ_MODRDN, name, args)
|
return this._mount(Protocol.LDAP_REQ_MODRDN, name, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -666,7 +633,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.operations.LDAP_REQ_SEARCH, name, args)
|
return this._mount(Protocol.LDAP_REQ_SEARCH, name, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -680,7 +647,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.operations.LDAP_REQ_UNBIND, 'unbind', args, true)
|
return this._mount(Protocol.LDAP_REQ_UNBIND, 'unbind', args, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
Server.prototype.use = function use () {
|
Server.prototype.use = function use () {
|
||||||
|
@ -701,13 +668,13 @@ Server.prototype.after = function () {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// All these just re-expose the requisite net.Server APIs
|
// All these just reexpose 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 = '127.0.0.1'
|
host = '0.0.0.0'
|
||||||
}
|
}
|
||||||
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
|
||||||
|
@ -750,10 +717,12 @@ 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 (Object.prototype.toString.call(_dn) === '[object LdapDn]') {
|
if (_dn instanceof dn.DN) {
|
||||||
name = _dn.toString()
|
name = _dn.toString()
|
||||||
} else {
|
} else {
|
||||||
name = _dn
|
name = _dn
|
||||||
|
@ -780,10 +749,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 (Object.prototype.toString.call(_dn) === '[object LdapDn]') {
|
if (_dn instanceof dn.DN) {
|
||||||
const reversed = _dn.clone()
|
const reversed = _dn.clone()
|
||||||
reversed.reverse()
|
reversed.rdns.reverse()
|
||||||
reversedRDNsToKeys[reversed.toString()] = key
|
reversedRDNsToKeys[reversed.format()] = key
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const output = []
|
const output = []
|
||||||
|
@ -800,15 +769,17 @@ Server.prototype._sortedRouteKeys = function _sortedRouteKeys () {
|
||||||
return this._routeKeyCache
|
return this._routeKeyCache
|
||||||
}
|
}
|
||||||
|
|
||||||
Server.prototype._getHandlerChain = function _getHandlerChain (req) {
|
Server.prototype._getHandlerChain = function _getHandlerChain (req, res) {
|
||||||
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.operations.LDAP_REQ_BIND &&
|
if (req.protocolOp === Protocol.LDAP_REQ_BIND &&
|
||||||
req.dn.toString() === '' &&
|
req.dn.toString() === '' &&
|
||||||
req.credentials === '') {
|
req.credentials === '') {
|
||||||
return {
|
return {
|
||||||
|
@ -820,7 +791,7 @@ Server.prototype._getHandlerChain = function _getHandlerChain (req) {
|
||||||
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.operations.LDAP_REQ_EXTENSION) {
|
if (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) {
|
||||||
route = routes[req.requestName]
|
route = routes[req.requestName]
|
||||||
if (route) {
|
if (route) {
|
||||||
return {
|
return {
|
||||||
|
@ -833,7 +804,7 @@ Server.prototype._getHandlerChain = function _getHandlerChain (req) {
|
||||||
handlers: [noExOpHandler]
|
handlers: [noExOpHandler]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (req.protocolOp === Protocol.operations.LDAP_REQ_UNBIND) {
|
} else if (req.protocolOp === Protocol.LDAP_REQ_UNBIND) {
|
||||||
route = routes.unbind
|
route = routes.unbind
|
||||||
if (route) {
|
if (route) {
|
||||||
return {
|
return {
|
||||||
|
@ -846,7 +817,7 @@ Server.prototype._getHandlerChain = function _getHandlerChain (req) {
|
||||||
handlers: [defaultNoOpHandler]
|
handlers: [defaultNoOpHandler]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (req.protocolOp === Protocol.operations.LDAP_REQ_ABANDON) {
|
} else if (req.protocolOp === Protocol.LDAP_REQ_ABANDON) {
|
||||||
return {
|
return {
|
||||||
backend: self,
|
backend: self,
|
||||||
handlers: [defaultNoOpHandler]
|
handlers: [defaultNoOpHandler]
|
||||||
|
@ -854,11 +825,11 @@ Server.prototype._getHandlerChain = function _getHandlerChain (req) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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') ? DN.fromString(req.dn) : req.dn
|
const testDN = (typeof (req.dn) === 'string') ? '' : 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]
|
||||||
|
@ -905,7 +876,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.fromString(name), backend)
|
const route = this._getRoute(notDN ? name : dn.parse(name), backend)
|
||||||
|
|
||||||
const chain = this._chain.slice()
|
const chain = this._chain.slice()
|
||||||
argv.slice(index).forEach(function (a) {
|
argv.slice(index).forEach(function (a) {
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
const querystring = require('querystring')
|
const querystring = require('querystring')
|
||||||
const url = require('url')
|
const url = require('url')
|
||||||
const { DN } = require('@ldapjs/dn')
|
const dn = require('./dn')
|
||||||
const filter = require('@ldapjs/filter')
|
const filter = require('./filters/')
|
||||||
|
|
||||||
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.fromString(u.pathname) : u.pathname
|
u.DN = parseDN ? dn.parse(u.pathname) : u.pathname
|
||||||
}
|
}
|
||||||
|
|
||||||
if (u.search) {
|
if (u.search) {
|
||||||
|
|
64
package.json
64
package.json
|
@ -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": "3.0.7",
|
"version": "2.3.2",
|
||||||
"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": {
|
||||||
"@ldapjs/asn1": "^2.0.0",
|
"abstract-logging": "^2.0.0",
|
||||||
"@ldapjs/attribute": "^1.0.0",
|
"asn1": "^0.2.4",
|
||||||
"@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.1",
|
"vasync": "^2.2.0",
|
||||||
"verror": "^1.10.1"
|
"verror": "^1.8.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fastify/pre-commit": "^2.0.2",
|
"eslint": "^7.20.0",
|
||||||
"eslint": "^8.44.0",
|
"eslint-config-standard": "^16.0.2",
|
||||||
"eslint-config-standard": "^17.0.0",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
"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": "6.1.1",
|
"eslint-plugin-promise": "^5.1.0",
|
||||||
"front-matter": "^4.0.2",
|
"front-matter": "^4.0.2",
|
||||||
"get-port": "^5.1.1",
|
"get-port": "^5.1.1",
|
||||||
"highlight.js": "^11.7.0",
|
"highlight.js": "^11.0.1",
|
||||||
"marked": "^4.2.12",
|
"husky": "^4.2.5",
|
||||||
"tap": "^16.3.7"
|
"marked": "^4.0.0",
|
||||||
|
"tap": "15.1.6"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "tap --no-cov -R terse",
|
"test": "tap --no-cov",
|
||||||
"test:ci": "tap --coverage-report=lcovonly -R terse",
|
"test:ci": "tap --coverage-report=lcovonly",
|
||||||
"test:cov": "tap -R terse",
|
"test:cov": "tap",
|
||||||
"test:cov:html": "tap --coverage-report=html -R terse",
|
"test:cov:html": "tap --coverage-report=html",
|
||||||
"test:watch": "tap -n -w --no-coverage-report -R terse",
|
"test:watch": "tap -n -w --no-coverage-report",
|
||||||
"test:integration": "tap --no-cov -R terse 'test-integration/**/*.test.js'",
|
"test:integration": "tap --no-cov 'test-integration/**/*.test.js'",
|
||||||
"test:integration:local": "docker-compose up -d --wait && npm run test:integration ; docker-compose down",
|
"test:integration:local": "docker-compose up -d && 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"
|
||||||
},
|
},
|
||||||
"pre-commit": [
|
"husky": {
|
||||||
"lint:ci",
|
"hooks": {
|
||||||
"test"
|
"pre-commit": "npm run lint:ci && npm run test"
|
||||||
]
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
'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()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,56 +0,0 @@
|
||||||
'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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,55 +0,0 @@
|
||||||
'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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,91 +0,0 @@
|
||||||
'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()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
'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()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,87 +0,0 @@
|
||||||
'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()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -10,14 +10,7 @@ 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 => {
|
||||||
// 2023-08-15: disabling this 265 character string until a bug can be
|
const longStr = 'a292979f2c86d513d48bbb9786b564b3c5228146e5ba46f404724e322544a7304a2b1049168803a5485e2d57a544c6a0d860af91330acb77e5907a9e601ad1227e80e0dc50abe963b47a004f2c90f570450d0e920d15436fdc771e3bdac0487a9735473ed3a79361d1778d7e53a7fb0e5f01f97a75ef05837d1d5496fc86968ff47fcb64'
|
||||||
// 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)
|
||||||
|
@ -55,9 +48,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.equal(value, 'dn:cn=Philip J. Fry,ou=people,dc=planetexpress,dc=com')
|
t.is(value, 'dn:cn=Philip J. Fry,ou=people,dc=planetexpress,dc=com')
|
||||||
t.ok(res)
|
t.ok(res)
|
||||||
t.equal(res.status, 0)
|
t.is(res.status, 0)
|
||||||
|
|
||||||
client.unbind(t.end)
|
client.unbind(t.end)
|
||||||
})
|
})
|
||||||
|
@ -81,15 +74,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.equal(result.status, 0)
|
t.is(result.status, 0)
|
||||||
t.equal(results.length === 1, true)
|
t.is(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.values)
|
t.ok(memberAttr.vals)
|
||||||
t.type(memberAttr.values, Array)
|
t.type(memberAttr.vals, Array)
|
||||||
t.equal(memberAttr.values.length, 2000)
|
t.is(memberAttr.vals.length, 2000)
|
||||||
|
|
||||||
client.unbind(t.end)
|
client.unbind(t.end)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 'latest'
|
|
||||||
},
|
|
||||||
|
|
||||||
rules: {
|
rules: {
|
||||||
'no-shadow': 'off'
|
'no-shadow': 'off'
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
'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()
|
||||||
|
})
|
|
@ -0,0 +1,253 @@
|
||||||
|
'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()
|
||||||
|
})
|
|
@ -6,19 +6,8 @@ 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
|
||||||
|
@ -55,7 +44,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.responseValue = 'u:xxyyz@EXAMPLE.NET'
|
res.value = 'u:xxyyz@EXAMPLE.NET'
|
||||||
res.end()
|
res.end()
|
||||||
return next()
|
return next()
|
||||||
})
|
})
|
||||||
|
@ -99,21 +88,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)
|
||||||
|
@ -153,14 +142,13 @@ 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(new SearchResultEntry({
|
res.send({
|
||||||
messageId: res.id,
|
dn: util.format('o=%d, cn=paged', i),
|
||||||
entry: `o=${i},cn=paged`,
|
attributes: {
|
||||||
attributes: Attribute.fromObject({
|
|
||||||
o: [i],
|
o: [i],
|
||||||
objectclass: ['pagedResult']
|
objectclass: ['pagedResult']
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
@ -168,17 +156,13 @@ 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 === controls.PagedResultsControl.OID) {
|
if (control.type === ldap.PagedResultsControl.OID) {
|
||||||
pageSize = control.value.size
|
pageSize = control.value.size
|
||||||
cookie = control.value.cookie
|
cookie = control.value.cookie
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!cookie || Buffer.isBuffer(cookie) === false) {
|
if (cookie && Buffer.isBuffer(cookie)) {
|
||||||
// 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) {
|
||||||
|
@ -192,7 +176,7 @@ tap.beforeEach((t) => {
|
||||||
} else {
|
} else {
|
||||||
resultCookie = Buffer.from('')
|
resultCookie = Buffer.from('')
|
||||||
}
|
}
|
||||||
res.addControl(new controls.PagedResultsControl({
|
res.controls.push(new ldap.PagedResultsControl({
|
||||||
value: {
|
value: {
|
||||||
size: pageSize, // correctness not required here
|
size: pageSize, // correctness not required here
|
||||||
cookie: resultCookie
|
cookie: resultCookie
|
||||||
|
@ -200,8 +184,11 @@ 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
|
||||||
|
@ -497,7 +484,7 @@ tap.test('add success', function (t) {
|
||||||
const attrs = [
|
const attrs = [
|
||||||
new Attribute({
|
new Attribute({
|
||||||
type: 'cn',
|
type: 'cn',
|
||||||
values: ['test']
|
vals: ['test']
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
t.context.client.add('cn=add, ' + SUFFIX, attrs, function (err, res) {
|
t.context.client.add('cn=add, ' + SUFFIX, attrs, function (err, res) {
|
||||||
|
@ -522,8 +509,8 @@ tap.test('add success with object', function (t) {
|
||||||
})
|
})
|
||||||
|
|
||||||
tap.test('add buffer', function (t) {
|
tap.test('add buffer', function (t) {
|
||||||
const { BerReader } = require('@ldapjs/asn1')
|
const { BerReader } = require('asn1')
|
||||||
const dn = `cn=add,${SUFFIX}`
|
const dn = `cn=add, ${SUFFIX}`
|
||||||
const attribute = 'thumbnailPhoto'
|
const attribute = 'thumbnailPhoto'
|
||||||
const binary = 0xa5
|
const binary = 0xa5
|
||||||
const entry = {
|
const entry = {
|
||||||
|
@ -532,7 +519,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, 48)
|
t.equal(data.byteLength, 49)
|
||||||
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)
|
||||||
|
@ -634,7 +621,7 @@ tap.test('modify success', function (t) {
|
||||||
type: 'Replace',
|
type: 'Replace',
|
||||||
modification: new Attribute({
|
modification: new Attribute({
|
||||||
type: 'cn',
|
type: 'cn',
|
||||||
values: ['test']
|
vals: ['test']
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
t.context.client.modify('cn=modify, ' + SUFFIX, change, function (err, res) {
|
t.context.client.modify('cn=modify, ' + SUFFIX, change, function (err, res) {
|
||||||
|
@ -645,11 +632,26 @@ 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: new Attribute({ type: 'cn', values: [null] })
|
modification: { cn: 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)
|
||||||
|
@ -665,7 +667,7 @@ tap.test('modify array success', function (t) {
|
||||||
operation: 'Replace',
|
operation: 'Replace',
|
||||||
modification: new Attribute({
|
modification: new Attribute({
|
||||||
type: 'cn',
|
type: 'cn',
|
||||||
values: ['test']
|
vals: ['test']
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
new Change({
|
new Change({
|
||||||
|
@ -683,6 +685,22 @@ 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)
|
||||||
|
@ -711,42 +729,47 @@ 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 { ModifyDnRequest } = messages
|
const { BerReader, BerWriter } = require('asn1')
|
||||||
|
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({
|
||||||
const req = new ModifyDnRequest({
|
entry: entry,
|
||||||
entry,
|
deleteOldRdn: deleteOldRdn,
|
||||||
deleteOldRdn,
|
controls: []
|
||||||
newRdn,
|
|
||||||
newSuperior
|
|
||||||
})
|
})
|
||||||
|
req.newRdn = newRdn
|
||||||
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.newSuperior = newSuperior
|
||||||
t.equal(req.newRdn.toString(), 'cn=Test User')
|
req._toBer(ber)
|
||||||
t.equal(req.deleteOldRdn, true)
|
const reader = new BerReader(ber.buffer)
|
||||||
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(), entry)
|
||||||
|
t.equal(reader.readString(), newRdn)
|
||||||
|
t.equal(reader.readBoolean(), deleteOldRdn)
|
||||||
|
t.equal(reader.readByte(), 0x80)
|
||||||
|
reader.readLength()
|
||||||
|
t.equal(reader._len, newSuperior.length)
|
||||||
|
reader._buf[--reader._offset] = 0x4
|
||||||
|
t.equal(reader.readString(), newSuperior)
|
||||||
t.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 SearchResultEntry)
|
t.ok(entry instanceof ldap.SearchEntry)
|
||||||
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) {
|
||||||
|
@ -754,37 +777,7 @@ 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 SearchResultDone)
|
t.ok(res instanceof ldap.SearchResponse)
|
||||||
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()
|
||||||
|
@ -851,7 +844,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 SearchRequest)
|
t2.ok(searchRequest instanceof ldap.SearchRequest)
|
||||||
if (currentSearchRequest === null) {
|
if (currentSearchRequest === null) {
|
||||||
t2.equal(countPages, 0)
|
t2.equal(countPages, 0)
|
||||||
}
|
}
|
||||||
|
@ -862,7 +855,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()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -878,7 +871,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1002,12 +995,7 @@ tap.test('search paged', { timeout: 10000 }, function (t) {
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
// We are skipping the ServerSideSorting test because we have skipped
|
tap.test('search - sssvlv', { timeout: 10000 }, function (t) {
|
||||||
// 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(
|
||||||
|
@ -1037,7 +1025,6 @@ tap.test('search - sssvlv', { timeout: 10000, skip: true }, 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(
|
||||||
|
@ -1068,9 +1055,7 @@ tap.test('search - sssvlv', { timeout: 10000, skip: true }, function (t) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('vlv - first page', { skip: true }, function (t2) {
|
t.test('vlv - first page', 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: {
|
||||||
|
@ -1112,10 +1097,7 @@ tap.test('search - sssvlv', { timeout: 10000, skip: true }, 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: {
|
||||||
|
@ -1157,7 +1139,6 @@ tap.test('search - sssvlv', { timeout: 10000, skip: true }, function (t) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1173,7 +1154,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 SearchResultReference)
|
t.ok(referral instanceof ldap.SearchReference)
|
||||||
t.ok(referral.uris)
|
t.ok(referral.uris)
|
||||||
t.ok(referral.uris.length)
|
t.ok(referral.uris.length)
|
||||||
})
|
})
|
||||||
|
@ -1182,7 +1163,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 SearchResultDone)
|
t.ok(res instanceof ldap.SearchResponse)
|
||||||
t.equal(res.status, 0)
|
t.equal(res.status, 0)
|
||||||
t.equal(gotEntry, 0)
|
t.equal(gotEntry, 0)
|
||||||
t.ok(gotReferral)
|
t.ok(gotReferral)
|
||||||
|
@ -1199,13 +1180,14 @@ 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 SearchResultDone)
|
t.ok(res instanceof ldap.SearchResponse)
|
||||||
t.equal(res.status, 0)
|
t.equal(res.status, 0)
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
@ -1218,16 +1200,12 @@ 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.pojo
|
const obj = entry.toObject()
|
||||||
t.equal('dc=empty', obj.objectName)
|
t.equal('dc=empty', obj.dn)
|
||||||
|
t.ok(obj.member)
|
||||||
const member = entry.attributes[0]
|
t.equal(obj.member.length, 0)
|
||||||
t.ok(member)
|
t.ok(obj['member;range=0-1'])
|
||||||
t.equal(member.values.length, 0)
|
t.ok(obj['member;range=0-1'].length)
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -1235,7 +1213,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 SearchResultDone)
|
t.ok(res instanceof ldap.SearchResponse)
|
||||||
t.equal(res.status, 0)
|
t.equal(res.status, 0)
|
||||||
t.equal(gotEntry, 1)
|
t.equal(gotEntry, 1)
|
||||||
t.end()
|
t.end()
|
||||||
|
@ -1252,12 +1230,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 SearchResultEntry)
|
t.ok(entry instanceof ldap.SearchEntry)
|
||||||
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].values[0], expect.toString('base64'))
|
t.equal(entry.attributes[0].vals[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'))
|
||||||
|
|
||||||
|
@ -1266,6 +1244,7 @@ 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) {
|
||||||
|
@ -1273,7 +1252,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 SearchResultDone)
|
t.ok(res instanceof ldap.SearchResponse)
|
||||||
t.equal(res.status, 0)
|
t.equal(res.status, 0)
|
||||||
t.equal(gotEntry, 1)
|
t.equal(gotEntry, 1)
|
||||||
t.end()
|
t.end()
|
||||||
|
@ -1292,11 +1271,12 @@ 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 SearchResultEntry)
|
t.ok(entry instanceof ldap.SearchEntry)
|
||||||
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) {
|
||||||
|
@ -1304,7 +1284,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 SearchResultDone)
|
t.ok(res instanceof ldap.SearchResponse)
|
||||||
t.equal(res.status, 0)
|
t.equal(res.status, 0)
|
||||||
t.equal(gotEntry, 2)
|
t.equal(gotEntry, 2)
|
||||||
t.end()
|
t.end()
|
||||||
|
@ -1323,12 +1303,13 @@ 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 SearchResultEntry)
|
t.ok(entry instanceof ldap.SearchEntry)
|
||||||
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) {
|
||||||
|
@ -1336,7 +1317,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 SearchResultDone)
|
t.ok(res instanceof ldap.SearchResponse)
|
||||||
t.equal(res.status, 0)
|
t.equal(res.status, 0)
|
||||||
t.equal(gotEntry, 2)
|
t.equal(gotEntry, 2)
|
||||||
t.end()
|
t.end()
|
||||||
|
@ -1673,7 +1654,7 @@ tap.test('connection timeout', function (t) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
tap.test('emitError', function (t) {
|
tap.only('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({
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const { test } = require('tap')
|
const { test } = require('tap')
|
||||||
const { BerReader, BerWriter } = require('@ldapjs/asn1')
|
const { BerReader, BerWriter } = require('asn1')
|
||||||
const { Control, getControl } = require('../../lib')
|
const { Control, getControl } = require('../../lib')
|
||||||
|
|
||||||
test('new no args', function (t) {
|
test('new no args', function (t) {
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
'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()
|
||||||
|
})
|
|
@ -0,0 +1,61 @@
|
||||||
|
'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()
|
||||||
|
})
|
|
@ -0,0 +1,99 @@
|
||||||
|
// 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()
|
||||||
|
})
|
|
@ -0,0 +1,95 @@
|
||||||
|
'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()
|
||||||
|
})
|
|
@ -0,0 +1,105 @@
|
||||||
|
'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()
|
||||||
|
})
|
|
@ -0,0 +1,94 @@
|
||||||
|
'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()
|
||||||
|
})
|
|
@ -0,0 +1,68 @@
|
||||||
|
'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()
|
||||||
|
})
|
|
@ -0,0 +1,234 @@
|
||||||
|
'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()
|
||||||
|
})
|
|
@ -0,0 +1,54 @@
|
||||||
|
'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
Loading…
Reference in New Issue