pax_global_header00006660000000000000000000000064142477730420014524gustar00rootroot0000000000000052 comment=92dfc80fd169f889cf2e0de53ff140ef2887c1c7 node-ldapjs-2.3.3/000077500000000000000000000000001424777304200137315ustar00rootroot00000000000000node-ldapjs-2.3.3/.eslintignore000066400000000000000000000000531424777304200164320ustar00rootroot00000000000000node_modules/ coverage/ .nyc_output/ docs/ node-ldapjs-2.3.3/.eslintrc.js000066400000000000000000000004201424777304200161640ustar00rootroot00000000000000module.exports = { env: { commonjs: true, es2021: true, node: true }, extends: [ 'standard' ], rules: { 'no-shadow': 'error', 'no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }] } } node-ldapjs-2.3.3/.github/000077500000000000000000000000001424777304200152715ustar00rootroot00000000000000node-ldapjs-2.3.3/.github/dependabot.yml000066400000000000000000000007071424777304200201250ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" # versioning-strategy: increase-if-necessary directory: "/" schedule: interval: "weekly" day: "saturday" time: "03:00" timezone: "America/New_York" - package-ecosystem: "npm" versioning-strategy: increase-if-necessary directory: "/" schedule: interval: "weekly" day: "saturday" time: "03:00" timezone: "America/New_York" node-ldapjs-2.3.3/.github/workflows/000077500000000000000000000000001424777304200173265ustar00rootroot00000000000000node-ldapjs-2.3.3/.github/workflows/docs.yml000066400000000000000000000013641424777304200210050ustar00rootroot00000000000000name: 'Update Docs' on: push: branches: - master jobs: docs: name: Update Docs runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '14' - name: Install Packages run: npm install - name: Build Docs run: npm run docs - name: Deploy 🚢 uses: cpina/github-action-push-to-another-repository@master env: API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} with: source-directory: 'public' destination-github-username: 'ldapjs' destination-repository-name: 'ldapjs.github.io' user-email: 'bot@ldapjs.org' target-branch: 'gh-pages' node-ldapjs-2.3.3/.github/workflows/integration.yml000066400000000000000000000012371424777304200223770ustar00rootroot00000000000000name: 'Integration Tests' # Notes: # https://github.community/t5/GitHub-Actions/Github-Actions-services-not-reachable/m-p/30739/highlight/true#M538 on: pull_request: branches: - master jobs: baseline: name: Baseline Tests runs-on: ubuntu-latest services: openldap: image: ghcr.io/ldapjs/docker-test-openldap/openldap:latest ports: - 389:389 - 636:636 steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 'lts/*' - name: Install Packages run: npm install - name: Run Tests run: npm run test:integration node-ldapjs-2.3.3/.github/workflows/main.yml000066400000000000000000000022631424777304200210000ustar00rootroot00000000000000name: 'Lint And Test' on: push: branches: - master pull_request: branches: - master jobs: lint: name: Lint Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 - name: Install Packages run: npm install - name: Lint Code run: npm run lint:ci run_tests: name: Unit Tests strategy: matrix: os: - ubuntu-latest - windows-latest node: - 10.13.0 - 10.x - 12.x - 14.x runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - name: Install Packages run: npm install - name: Run Tests run: npm run test:ci - 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 node-ldapjs-2.3.3/.gitignore000066400000000000000000000016671424777304200157330ustar00rootroot00000000000000*.ldif *.tar.* *.tgz # Lock files pnpm-lock.yaml shrinkwrap.yaml package-lock.json yarn.lock # Logs logs *.log npm-debug.log* # Runtime data pids *.pid *.seed # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directory node_modules # Optional npm cache directory .npm # Optional REPL history .node_repl_history # 0x .__browserify_string_empty.js profile-* *.flamegraph # tap --cov .nyc_output/ # JetBrains IntelliJ IDEA .idea/ *.iml # VS Code .vscode/ # xcode build/* *.mode1 *.mode1v3 *.mode2v3 *.perspective *.perspectivev3 *.pbxuser *.xcworkspace xcuserdata # macOS .DS_Store # keys *.pem *.env.json *.env # built docs /public node-ldapjs-2.3.3/.taprc000066400000000000000000000000661424777304200150450ustar00rootroot00000000000000check-coverage: false files: - 'test/**/*.test.js' node-ldapjs-2.3.3/CHANGES.md000066400000000000000000000065041424777304200153300ustar00rootroot00000000000000# ldapjs Changelog ## 2.0.0 - Going foward, please see https://github.com/ldapjs/node-ldapjs/releases ## 1.0.2 - Update dtrace-provider dependency ## 1.0.1 - Update dependencies * assert-plus to 1.0.0 * bunyan to 1.8.3 * dashdash to 1.14.0 * backoff to 2.5.0 * once to 1.4.0 * vasync to 1.6.4 * verror to 1.8.1 * dtrace-provider to 0.7.0 - Drop any semblence of support for node 0.8.x ## 1.0.0 - Update dependencies * asn1 to 0.2.3 * bunyan to 1.5.1 * dtrace-provider to 0.6.0 - Removed pooled client - Removed custom formatting for GUIDs - Completely overhaul DN parsing/formatting - Add options for format preservation - Removed `spaced()` and `rndSpaced` from DN API - Fix parent/child rules regarding empty DNs - Request routing overhaul * #154 Route lookups do not depend on object property order * #111 Null ('') DN will act as catch-all - Add StartTLS support to client (Sponsored by: DoubleCheck Email Manager) - Improve robustness of client reconnect logic - Add 'resultError' event to client - Update paged search automation in client - Add Change.apply method for modifying objects - #143 Preserve raw Buffer value in Control objects - Test code coverage with node-istanbul - Convert tests to node-tape - Add controls for server-side sorting - #201 Replace nopt with dashdash - #134 Allow configuration of derefAliases client option - #197 Properly dispatch unbind requests - #196 Handle string ports properly in server.listen - Add basic server API tests - Store EqualityFilter value as Buffer - Run full test suite during 'make test' - #190 Add error code 123 from RFC4370 - #178 Perform strict presence testing on attribute vals - #183 Accept buffers or strings for cert/key in createServer - #180 Add '-i, --insecure' option and to all ldapjs-\* CLIs - #254 Allow simple client bind with empty credentials ## 0.7.1 - #169 Update dependencies * asn1 to 0.2.1 * pooling to 0.4.6 * assert-plus to 0.1.5 * bunyan to 0.22.1 - #173 Make dtrace-provider an optional dependency - #142 Improve parser error handling - #161 Properly handle close events on tls sockets - #163 Remove buffertools dependency - #162 Fix error event handling for pooled clients - #159 Allow ext request message to have a buffer value - #155 Make \*Filter.matches case insensitive for attrs ## 0.7.0 - #87 Minor update to ClientPool event pass-through - #145 Update pooling to 0.4.5 - #144 Fix unhandled error during client connection - Output ldapi:// URLs for UNIX domain sockets - Support extensible matching of caseIgnore and caseIgnoreSubstrings - Fix some ClientPool event handling - Improve DN formatting flexibility * Add 'spaced' function to DN objects allowing toggle of inter-RDN when rendering to a string. ('dc=test,dc=tld' vs 'dc=test, dc=tld') * Detect RDN spacing when parsing DN. - #128 Fix user can't bind with inmemory example - #139 Bump required tap version to 0.4.1 - Allow binding ldap server on an ephemeral port ## 0.6.3 - Update bunyan to 0.21.1 - Remove listeners on the right object (s/client/res/) - Replace log4js with bunyan for binaries - #127 socket is closed issue with pools - #122 Allow changing TLS connection options in client - #120 Fix a bug with formatting digits less than 16. - #118 Fix "failed to instantiate provider" warnings in console on SmartOS ## 0.6.2 - 0.1.0 **See git history** node-ldapjs-2.3.3/LICENSE000066400000000000000000000020571424777304200147420ustar00rootroot00000000000000Copyright (c) 2019 LDAPjs, All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE node-ldapjs-2.3.3/README.md000066400000000000000000000023171424777304200152130ustar00rootroot00000000000000# LDAPjs [![Build Status](https://github.com/ldapjs/node-ldapjs/workflows/Lint%20And%20Test/badge.svg)](https://github.com/ldapjs/node-ldapjs/actions) [![Coverage Status](https://coveralls.io/repos/github/ldapjs/node-ldapjs/badge.svg)](https://coveralls.io/github/ldapjs/node-ldapjs/) LDAPjs makes the LDAP protocol a first class citizen in Node.js. ## Usage For full docs, head on over to . ```javascript var ldap = require('ldapjs'); var server = ldap.createServer(); server.search('dc=example', function(req, res, next) { var obj = { dn: req.dn.toString(), attributes: { objectclass: ['organization', 'top'], o: 'example' } }; if (req.filter.matches(obj.attributes)) res.send(obj); res.end(); }); server.listen(1389, function() { console.log('ldapjs listening at ' + server.url); }); ``` To run that, assuming you've got the [OpenLDAP](http://www.openldap.org/) client on your system: ldapsearch -H ldap://localhost:1389 -x -b dc=example objectclass=* ## Installation npm install ldapjs DTrace support is included in ldapjs. To enable it, `npm install dtrace-provider`. ## License MIT. ## Bugs See . node-ldapjs-2.3.3/docker-compose.yml000066400000000000000000000002161424777304200173650ustar00rootroot00000000000000version: '3' services: openldap: image: ghcr.io/ldapjs/docker-test-openldap/openldap:latest ports: - 389:389 - 636:636 node-ldapjs-2.3.3/docs/000077500000000000000000000000001424777304200146615ustar00rootroot00000000000000node-ldapjs-2.3.3/docs/branding/000077500000000000000000000000001424777304200164455ustar00rootroot00000000000000node-ldapjs-2.3.3/docs/branding/public/000077500000000000000000000000001424777304200177235ustar00rootroot00000000000000node-ldapjs-2.3.3/docs/branding/public/CNAME000066400000000000000000000000131424777304200204630ustar00rootroot00000000000000ldapjs.org node-ldapjs-2.3.3/docs/branding/public/media/000077500000000000000000000000001424777304200210025ustar00rootroot00000000000000node-ldapjs-2.3.3/docs/branding/public/media/css/000077500000000000000000000000001424777304200215725ustar00rootroot00000000000000node-ldapjs-2.3.3/docs/branding/public/media/css/style.css000066400000000000000000000071501424777304200234470ustar00rootroot00000000000000 /* ---- general styles */ body { font: 13px "Lucida Grande", "Lucida Sans Unicode", arial, sans-serif; line-height: 1.53846; /* 20px */ color: #4a3f2d; } :focus:not(:focus-visible) { outline: 0; } h1,h2,h3 { font-weight:normal; } h3{ margin-bottom:0; } ul, li { margin:0px; padding:0px; } ul { margin-left:40px; } ul > li { list-style:disc; list-style-position:inside; margin:10px 0px; } hr { border:none; width:98%; margin-left:-10px; border-top:1px solid #CCCCCC; border-bottom:1px solid #FFFFFF; } code, pre { border:1px solid #CCCCCC; background:#F2F0EE; -webkit-border-radius:2px; -moz-border-radius:2px; border-radius:2px; white-space:pre-wrap; } code { padding: 0 0.2em; } pre { margin: 1em 0; padding: .75em; overflow: auto; padding:10px 1.2em; margin-top:0; margin-bottom:20px; } pre code { border: medium none; padding: 0; } a code { text-decoration: underline; } a { color:#FD6512; text-decoration:none; } h4 { font-size: 85%; margin: 0; padding: 0; line-height: 1em; display: inline; } /* ---- header and sidebar */ #header { background:#C3BDB3; background:#1C313C; height:66px; left:0px; position:absolute; top:0px; width:100%; z-index:1; font-size:0.7em; } #header h1 { width: 424px; height: 35px; display:block; background: url(../img/logo.svg) no-repeat; line-height:2.1em; padding:0; padding-left:140px; margin-top:18px; margin-left:20px; color:white; text-transform: uppercase; } #sidebar { background-color:#EDEBEA; bottom:0px; left:0px; overflow:auto; padding:20px 0px 0px 15px; position:absolute; top:66px; width:265px; z-index:1; } #content { top:64px; bottom:0px; right:0px; left:290px; padding:20px 30px 400px; position:absolute; overflow:auto; z-index:0; } #sidebar h1 { font-size:1.2em; padding:0px; margin-top:15px; margin-bottom:3px; } #sidebar ul { margin:3px 0 10px 0; } #sidebar ul ul { margin:3px 0 5px 10px; } #sidebar li { margin:0; padding:0; font-size:0.9em; } #sidebar li, #sidebar li a { color:#5C5954; list-style:none; padding:1px 0px 1px 2px; } /* ---- intro */ .intro { color:#29231A; padding: 22px 25px; background: #EDEBEA; -webkit-border-radius: 5px; -moz-border-radius: 5px; -o-border-radius: 5px; border-radius: 5px; margin-bottom:40px; } .intro h1 { color: #1C313C; } .intro h3 { margin: 5px 0px 3px; font-size: 100%; font-weight: bold; } .intro ul { list-style-type:disc; padding-left:20px; margin-left:0; } .intro ul li{ margin:0; } .intro p { padding-left:20px; margin: 5px 0px 3px; } h2 { overflow: auto; margin-top: 60px; border-top: 2px solid #979592; z-index: 3; } h1 + h2 { margin-top: 0px; } h2 span { background: #979592; float:right; color:#fff; margin:0; margin-left:3px; padding:0.3em 0.7em; font-size: 0.55em; word-spacing: 0.8em; /* separate verb from path */ color:#fff; } /*---- print media */ @media print { body { background:white; color:black; margin:0; } #sidebar { display: none; } #content { position: relative; padding: 5px; left: 0px; top: 0px; } h1, h2, h4 { page-break-after: avoid; } pre { page-break-inside: avoid; } } /* tables still need cellspacing="0" in the markup */ table { border-collapse:collapse; border-spacing:0; margin: 20px 0; } th, td { border: solid #aaa; border-width: 1px 0; line-height: 23px; padding: 0 12px; text-align: left; vertical-align: text-bottom; } th { border-collapse: separate; } tbody tr:nth-child(odd) { background-color: #f2f0ee; } node-ldapjs-2.3.3/docs/branding/public/media/img/000077500000000000000000000000001424777304200215565ustar00rootroot00000000000000node-ldapjs-2.3.3/docs/branding/public/media/img/logo.svg000066400000000000000000000063601424777304200232440ustar00rootroot00000000000000 node-ldapjs-2.3.3/docs/branding/template.html000066400000000000000000000017401424777304200211500ustar00rootroot00000000000000 %(title)s
%(content)s
node-ldapjs-2.3.3/docs/client.md000066400000000000000000000406671424777304200164760ustar00rootroot00000000000000--- title: Client API | ldapjs --- # ldapjs Client API
This document covers the ldapjs client API and assumes that you are familiar with LDAP. If you're not, read the [guide](guide.html) first.
# Create a client The code to create a new client looks like: ```js const ldap = require('ldapjs'); const client = ldap.createClient({ url: ['ldap://127.0.0.1:1389', 'ldap://127.0.0.2:1389'] }); client.on('error', (err) => { // handle connection error }) ``` You can use `ldap://` or `ldaps://`; the latter would connect over SSL (note that this will not use the LDAP TLS extended operation, but literally an SSL connection to port 636, as in LDAP v2). The full set of options to create a client is: |Attribute |Description | |---------------|-----------------------------------------------------------| |url |A string or array of valid LDAP URL(s) (proto/host/port) | |socketPath |Socket path if using AF\_UNIX sockets | |log |A compatible logger instance (Default: no-op logger) | |timeout |Milliseconds client should let operations live for before timing out (Default: Infinity)| |connectTimeout |Milliseconds client should wait before timing out on TCP connections (Default: OS default)| |tlsOptions |Additional options passed to TLS connection layer when connecting via `ldaps://` (See: The TLS docs for node.js)| |idleTimeout |Milliseconds after last activity before client emits idle event| |strictDN |Force strict DN parsing for client methods (Default is true)| |reconnect |Try to reconnect when the connection gets lost (Default is false)| ### url This parameter takes a single connection string or an array of connection strings as an input. In case an array is provided, the client tries to connect to the servers in given order. To achieve random server strategy (e.g. to distribute the load among the servers), please shuffle the array before passing it as an argument. ### Note On Logger A passed in logger is expected to conform to the [Bunyan](https://www.npmjs.com/package/bunyan) API. Specifically, the logger is expected to have a `child()` method. If a logger is supplied that does not have such a method, then a shim version is added that merely returns the passed in logger. Known compatible loggers are: + [Bunyan](https://www.npmjs.com/package/bunyan) + [Pino](https://www.npmjs.com/package/pino) ### Note On Error Handling The client is an `EventEmitter`. If you don't register an error handler and e.g. a connection error occurs, Node.js will print a stack trace and exit the process ([reference](https://nodejs.org/api/events.html#error-events)). ## Connection management As LDAP is a stateful protocol (as opposed to HTTP), having connections torn down from underneath you can be difficult to deal with. Several mechanisms have been provided to mitigate this trouble. ### Reconnect You can provide a Boolean option indicating if a reconnect should be tried. For more sophisticated control, you can provide an Object with the properties `initialDelay` (default: `100`), `maxDelay` (default: `10000`) and `failAfter` (default: `Infinity`). After the reconnect you maybe need to [bind](#bind) again. ## Client events The client is an `EventEmitter` and can emit the following events: |Event |Description | |---------------|----------------------------------------------------------| |error |General error | |connectRefused |Server refused connection. Most likely bad authentication | |connectTimeout |Server timeout | |connectError |Socket connection error | |setupError |Setup error after successful connection | |socketTimeout |Socket timeout | |resultError |Search result error | |timeout |Search result timeout | |destroy |After client is disconnected | |end |Socket end event | |close |Socket closed | |connect |Client connected | |idle |Idle timeout reached | ## Common patterns The last two parameters in every API are `controls` and `callback`. `controls` can be either a single instance of a `Control` or an array of `Control` objects. You can, and probably will, omit this option. Almost every operation has the callback form of `function(err, res)` where err will be an instance of an `LDAPError` (you can use `instanceof` to switch). You probably won't need to check the `res` parameter, but it's there if you do. # bind `bind(dn, password, controls, callback)` Performs a bind operation against the LDAP server. The bind API only allows LDAP 'simple' binds (equivalent to HTTP Basic Authentication) for now. Note that all client APIs can optionally take an array of `Control` objects. You probably don't need them though... Example: ```js client.bind('cn=root', 'secret', (err) => { assert.ifError(err); }); ``` # add `add(dn, entry, controls, callback)` Performs an add operation against the LDAP server. Allows you to add an entry (which is just a plain JS object), and as always, controls are optional. Example: ```js const entry = { cn: 'foo', sn: 'bar', email: ['foo@bar.com', 'foo1@bar.com'], objectclass: 'fooPerson' }; client.add('cn=foo, o=example', entry, (err) => { assert.ifError(err); }); ``` # compare `compare(dn, attribute, value, controls, callback)` Performs an LDAP compare operation with the given attribute and value against the entry referenced by dn. Example: ```js client.compare('cn=foo, o=example', 'sn', 'bar', (err, matched) => { assert.ifError(err); console.log('matched: ' + matched); }); ``` # del `del(dn, controls, callback)` Deletes an entry from the LDAP server. Example: ```js client.del('cn=foo, o=example', (err) => { assert.ifError(err); }); ``` # exop `exop(name, value, controls, callback)` Performs an LDAP extended operation against an LDAP server. `name` is typically going to be an OID (well, the RFC says it must be; however, ldapjs has no such restriction). `value` is completely arbitrary, and is whatever the exop says it should be. Example (performs an LDAP 'whois' extended op): ```js client.exop('1.3.6.1.4.1.4203.1.11.3', (err, value, res) => { assert.ifError(err); console.log('whois: ' + value); }); ``` # modify `modify(name, changes, controls, callback)` Performs an LDAP modify operation against the LDAP server. This API requires you to pass in a `Change` object, which is described below. Note that you can pass in a single `Change` or an array of `Change` objects. Example: ```js const change = new ldap.Change({ operation: 'add', modification: { pets: ['cat', 'dog'] } }); client.modify('cn=foo, o=example', change, (err) => { assert.ifError(err); }); ``` ## Change A `Change` object maps to the LDAP protocol of a modify change, and requires you to set the `operation` and `modification`. The `operation` is a string, and must be one of: | Operation | Description | |-----------|-------------| | replace | Replaces the attribute referenced in `modification`. If the modification has no values, it is equivalent to a delete. | | add | Adds the attribute value(s) referenced in `modification`. The attribute may or may not already exist. | | delete | Deletes the attribute (and all values) referenced in `modification`. | `modification` is just a plain old JS object with the values you want. # modifyDN `modifyDN(dn, newDN, controls, callback)` Performs an LDAP modifyDN (rename) operation against an entry in the LDAP server. A couple points with this client API: * There is no ability to set "keep old dn." It's always going to flag the old dn to be purged. * The client code will automatically figure out if the request is a "new superior" request ("new superior" means move to a different part of the tree, as opposed to just renaming the leaf). Example: ```js client.modifyDN('cn=foo, o=example', 'cn=bar', (err) => { assert.ifError(err); }); ``` # search `search(base, options, controls, callback)` Performs a search operation against the LDAP server. The search operation is more complex than the other operations, so this one takes an `options` object for all the parameters. However, ldapjs makes some defaults for you so that if you pass nothing in, it's pretty much equivalent to an HTTP GET operation (i.e., base search against the DN, filter set to always match). Like every other operation, `base` is a DN string. Options can be a string representing a valid LDAP filter or an object containing the following fields: |Attribute |Description | |-----------|---------------------------------------------------| |scope |One of `base`, `one`, or `sub`. Defaults to `base`.| |filter |A string version of an LDAP filter (see below), or a programatically constructed `Filter` object. Defaults to `(objectclass=*)`.| |attributes |attributes to select and return (if these are set, the server will return *only* these attributes). Defaults to the empty set, which means all attributes. You can provide a string if you want a single attribute or an array of string for one or many.| |attrsOnly |boolean on whether you want the server to only return the names of the attributes, and not their values. Borderline useless. Defaults to false.| |sizeLimit |the maximum number of entries to return. Defaults to 0 (unlimited).| |timeLimit |the maximum amount of time the server should take in responding, in seconds. Defaults to 10. Lots of servers will ignore this.| |paged |enable and/or configure automatic result paging| Responses inside callback of the `search` method are an `EventEmitter` where you will get a notification for each `searchEntry` that comes back from the server. You will additionally be able to listen for a `searchRequest` , `searchReference`, `error` and `end` event. `searchRequest` is emitted immediately after every `SearchRequest` is sent with a `SearchRequest` parameter. You can do operations like `client.abandon` with `searchRequest.messageID` to abandon this search request. Note that the `error` event will only be for client/TCP errors, not LDAP error codes like the other APIs. You'll want to check the LDAP status code (likely for `0`) on the `end` event to assert success. LDAP search results can give you a lot of status codes, such as time or size exceeded, busy, inappropriate matching, etc., which is why this method doesn't try to wrap up the code matching. Example: ```js const opts = { filter: '(&(l=Seattle)(email=*@foo.com))', scope: 'sub', attributes: ['dn', 'sn', 'cn'] }; client.search('o=example', opts, (err, res) => { assert.ifError(err); res.on('searchRequest', (searchRequest) => { console.log('searchRequest: ', searchRequest.messageID); }); res.on('searchEntry', (entry) => { console.log('entry: ' + JSON.stringify(entry.object)); }); res.on('searchReference', (referral) => { console.log('referral: ' + referral.uris.join()); }); res.on('error', (err) => { console.error('error: ' + err.message); }); res.on('end', (result) => { console.log('status: ' + result.status); }); }); ``` ## Filter Strings The easiest way to write search filters is to write them compliant with RFC2254, which is "The string representation of LDAP search filters." Note that ldapjs doesn't support extensible matching, since it's one of those features that almost nobody actually uses in practice. Assuming you don't really want to read the RFC, search filters in LDAP are basically are a "tree" of attribute/value assertions, with the tree specified in prefix notation. For example, let's start simple, and build up a complicated filter. The most basic filter is equality, so let's assume you want to search for an attribute `email` with a value of `foo@bar.com`. The syntax would be: ``` (email=foo@bar.com) ``` ldapjs requires all filters to be surrounded by '()' blocks. Ok, that was easy. Let's now assume that you want to find all records where the email is actually just anything in the "@bar.com" domain and the location attribute is set to Seattle: ``` (&(email=*@bar.com)(l=Seattle)) ``` Now our filter is actually three LDAP filters. We have an `and` filter (single amp `&`), an `equality` filter `(the l=Seattle)`, and a `substring` filter. Substrings are wildcard filters. They use `*` as the wildcard. You can put more than one wildcard for a given string. For example you could do `(email=*@*bar.com)` to match any email of @bar.com or its subdomains like `"example@foo.bar.com"`. Now, let's say we also want to set our filter to include a specification that either the employeeType *not* be a manager nor a secretary: ``` (&(email=*@bar.com)(l=Seattle)(!(|(employeeType=manager)(employeeType=secretary)))) ``` The `not` character is represented as a `!`, the `or` as a single pipe `|`. It gets a little bit complicated, but it's actually quite powerful, and lets you find almost anything you're looking for. ## Paging Many LDAP server enforce size limits upon the returned result set (commonly 1000). In order to retrieve results beyond this limit, a `PagedResultControl` is passed between the client and server to iterate through the entire dataset. While callers could choose to do this manually via the `controls` parameter to `search()`, ldapjs has internal mechanisms to easily automate the process. The most simple way to use the paging automation is to set the `paged` option to true when performing a search: ```js const opts = { filter: '(objectclass=commonobject)', scope: 'sub', paged: true, sizeLimit: 200 }; client.search('o=largedir', opts, (err, res) => { assert.ifError(err); res.on('searchEntry', (entry) => { // do per-entry processing }); res.on('page', (result) => { console.log('page end'); }); res.on('error', (resErr) => { assert.ifError(resErr); }); res.on('end', (result) => { console.log('done '); }); }); ``` This will enable paging with a default page size of 199 (`sizeLimit` - 1) and will output all of the resulting objects via the `searchEntry` event. At the end of each result during the operation, a `page` event will be emitted as well (which includes the intermediate `searchResult` object). For those wanting more precise control over the process, an object with several parameters can be provided for the `paged` option. The `pageSize` parameter sets the size of result pages requested from the server. If no value is specified, it will fall back to the default (100 or `sizeLimit` - 1, to obey the RFC). The `pagePause` parameter allows back-pressure to be exerted on the paged search operation by pausing at the end of each page. When enabled, a callback function is passed as an additional parameter to `page` events. The client will wait to request the next page until that callback is executed. Here is an example where both of those parameters are used: ```js const queue = new MyWorkQueue(someSlowWorkFunction); const opts = { filter: '(objectclass=commonobject)', scope: 'sub', paged: { pageSize: 250, pagePause: true }, }; client.search('o=largerdir', opts, (err, res) => { assert.ifError(err); res.on('searchEntry', (entry) => { // Submit incoming objects to queue queue.push(entry); }); res.on('page', (result, cb) => { // Allow the queue to flush before fetching next page queue.cbWhenFlushed(cb); }); res.on('error', (resErr) => { assert.ifError(resErr); }); res.on('end', (result) => { console.log('done'); }); }); ``` # starttls `starttls(options, controls, callback)` Attempt to secure existing LDAP connection via STARTTLS. Example: ```js const opts = { ca: [fs.readFileSync('mycacert.pem')] }; client.starttls(opts, (err, res) => { assert.ifError(err); // Client communication now TLS protected }); ``` # unbind `unbind(callback)` Performs an unbind operation against the LDAP server. Note that unbind operation is not an opposite operation for bind. Unbinding results in disconnecting the client regardless of whether a bind operation was performed. The `callback` argument is optional as unbind does not have a response. Example: ```js client.unbind((err) => { assert.ifError(err); }); ``` node-ldapjs-2.3.3/docs/dn.md000066400000000000000000000066041424777304200156120ustar00rootroot00000000000000--- title: DN API | ldapjs --- # ldapjs DN API
This document covers the ldapjs DN API and assumes that you are familiar with LDAP. If you're not, read the [guide](guide.html) first.
DNs are LDAP distinguished names, and are composed of a set of RDNs (relative distinguished names). [RFC2253](http://www.ietf.org/rfc/rfc2253.txt) has the complete specification, but basically an RDN is an attribute value assertion with `=` as the seperator, like: `cn=foo` where 'cn' is 'commonName' and 'foo' is the value. You can have compound RDNs by using the `+` character: `cn=foo+sn=bar`. As stated above, DNs are a set of RDNs, typically separated with the `,` character, like: `cn=foo, ou=people, o=example`. This uniquely identifies an entry in the tree, and is read "bottom up". # parseDN(dnString) The `parseDN` API converts a string representation of a DN into an ldapjs DN object; in most cases this will be handled for you under the covers of the ldapjs framework, but if you need it, it's there. ```js const parseDN = require('ldapjs').parseDN; const dn = parseDN('cn=foo+sn=bar, ou=people, o=example'); console.log(dn.toString()); ``` # DN The DN object is largely what you'll be interacting with, since all the server APIs are setup to give you a DN object. ## childOf(dn) Returns a boolean indicating whether 'this' is a child of the passed in dn. The `dn` argument can be either a string or a DN. ```js server.add('o=example', (req, res, next) => { if (req.dn.childOf('ou=people, o=example')) { ... } else { ... } }); ``` ## parentOf(dn) The inverse of `childOf`; returns a boolean on whether or not `this` is a parent of the passed in dn. Like `childOf`, can take either a string or a DN. ```js server.add('o=example', (req, res, next) => { const dn = parseDN('ou=people, o=example'); if (dn.parentOf(req.dn)) { ... } else { ... } }); ``` ## equals(dn) Returns a boolean indicating whether `this` is equivalent to the passed in `dn` argument. `dn` can be a string or a DN. ```js server.add('o=example', (req, res, next) => { if (req.dn.equals('cn=foo, ou=people, o=example')) { ... } else { ... } }); ``` ## parent() Returns a DN object that is the direct parent of `this`. If there is no parent this can return `null` (e.g. `parseDN('o=example').parent()` will return null). ## format(options) Convert a DN object to string according to specified formatting options. These options are divided into two types. Preservation Options use data recorded during parsing to preserve details of the original DN. Modification options alter string formatting defaults. Preservation options _always_ take precedence over Modification Options. Preservation Options: - `keepOrder`: Order of multi-value RDNs. - `keepQuote`: RDN values which were quoted will remain so. - `keepSpace`: Leading/trailing spaces will be output. - `keepCase`: Parsed attribute name will be output instead of lowercased version. Modification Options: - `upperName`: RDN names will be uppercased instead of lowercased. - `skipSpace`: Disable trailing space after RDN separators ## setFormat(options) Sets the default `options` for string formatting when `toString` is called. It accepts the same parameters as `format`. ## toString() Returns the string representation of `this`. ```js server.add('o=example', (req, res, next) => { console.log(req.dn.toString()); }); ``` node-ldapjs-2.3.3/docs/errors.md000066400000000000000000000042101424777304200165140ustar00rootroot00000000000000--- title: Errors API | ldapjs --- # ldapjs Errors API
This document covers the ldapjs errors API and assumes that you are familiar with LDAP. If you're not, read the [guide](guide.html) first.
All errors in the ldapjs framework extend from an abstract error type called `LDAPError`. In addition to the properties listed below, all errors will have a `stack` property correctly set. In general, you'll be using the errors in ldapjs like: ```js const ldap = require('ldapjs'); const db = {}; server.add('o=example', (req, res, next) => { const parent = req.dn.parent(); if (parent) { if (!db[parent.toString()]) return next(new ldap.NoSuchObjectError(parent.toString())); } if (db[req.dn.toString()]) return next(new ldap.EntryAlreadyExistsError(req.dn.toString())); ... }); ``` I.e., if you just pass them into the `next()` handler, ldapjs will automatically return the appropriate LDAP error message, and stop the handler chain. All errors will have the following properties: ## code Returns the LDAP status code associated with this error. ## name The name of this error. ## message The message that will be returned to the client. # Complete list of LDAPError subclasses * OperationsError * ProtocolError * TimeLimitExceededError * SizeLimitExceededError * CompareFalseError * CompareTrueError * AuthMethodNotSupportedError * StrongAuthRequiredError * ReferralError * AdminLimitExceededError * UnavailableCriticalExtensionError * ConfidentialityRequiredError * SaslBindInProgressError * NoSuchAttributeError * UndefinedAttributeTypeError * InappropriateMatchingError * ConstraintViolationError * AttributeOrValueExistsError * InvalidAttriubteSyntaxError * NoSuchObjectError * AliasProblemError * InvalidDnSyntaxError * AliasDerefProblemError * InappropriateAuthenticationError * InvalidCredentialsError * InsufficientAccessRightsError * BusyError * UnavailableError * UnwillingToPerformError * LoopDetectError * NamingViolationError * ObjectclassViolationError * NotAllowedOnNonLeafError * NotAllowedOnRdnError * EntryAlreadyExistsError * ObjectclassModsProhibitedError * AffectsMultipleDsasError * OtherError node-ldapjs-2.3.3/docs/examples.md000066400000000000000000000345031424777304200170260ustar00rootroot00000000000000--- title: Examples | ldapjs --- # ldapjs Examples
This page contains a (hopefully) growing list of sample code to get you started with ldapjs.
# In-memory server ```js const ldap = require('ldapjs'); ///--- Shared handlers function authorize(req, res, next) { /* Any user may search after bind, only cn=root has full power */ const isSearch = (req instanceof ldap.SearchRequest); if (!req.connection.ldap.bindDN.equals('cn=root') && !isSearch) return next(new ldap.InsufficientAccessRightsError()); return next(); } ///--- Globals const SUFFIX = 'o=joyent'; const db = {}; const server = ldap.createServer(); server.bind('cn=root', (req, res, next) => { if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret') return next(new ldap.InvalidCredentialsError()); res.end(); return next(); }); server.add(SUFFIX, authorize, (req, res, next) => { const dn = req.dn.toString(); if (db[dn]) return next(new ldap.EntryAlreadyExistsError(dn)); db[dn] = req.toObject().attributes; res.end(); return next(); }); server.bind(SUFFIX, (req, res, next) => { const dn = req.dn.toString(); if (!db[dn]) return next(new ldap.NoSuchObjectError(dn)); if (!db[dn].userpassword) return next(new ldap.NoSuchAttributeError('userPassword')); if (db[dn].userpassword.indexOf(req.credentials) === -1) return next(new ldap.InvalidCredentialsError()); res.end(); return next(); }); server.compare(SUFFIX, authorize, (req, res, next) => { const dn = req.dn.toString(); if (!db[dn]) return next(new ldap.NoSuchObjectError(dn)); if (!db[dn][req.attribute]) return next(new ldap.NoSuchAttributeError(req.attribute)); const matches = false; const vals = db[dn][req.attribute]; for (const value of vals) { if (value === req.value) { matches = true; break; } } res.end(matches); return next(); }); server.del(SUFFIX, authorize, (req, res, next) => { const dn = req.dn.toString(); if (!db[dn]) return next(new ldap.NoSuchObjectError(dn)); delete db[dn]; res.end(); return next(); }); server.modify(SUFFIX, authorize, (req, res, next) => { const dn = req.dn.toString(); if (!req.changes.length) return next(new ldap.ProtocolError('changes required')); if (!db[dn]) return next(new ldap.NoSuchObjectError(dn)); const entry = db[dn]; for (const change of req.changes) { mod = change.modification; switch (change.operation) { case 'replace': if (!entry[mod.type]) return next(new ldap.NoSuchAttributeError(mod.type)); if (!mod.vals || !mod.vals.length) { delete entry[mod.type]; } else { entry[mod.type] = mod.vals; } break; case 'add': if (!entry[mod.type]) { entry[mod.type] = mod.vals; } else { for (const v of mod.vals) { if (entry[mod.type].indexOf(v) === -1) entry[mod.type].push(v); } } break; case 'delete': if (!entry[mod.type]) return next(new ldap.NoSuchAttributeError(mod.type)); delete entry[mod.type]; break; } } res.end(); return next(); }); server.search(SUFFIX, authorize, (req, res, next) => { const dn = req.dn.toString(); if (!db[dn]) return next(new ldap.NoSuchObjectError(dn)); let scopeCheck; switch (req.scope) { case 'base': if (req.filter.matches(db[dn])) { res.send({ dn: dn, attributes: db[dn] }); } res.end(); return next(); case 'one': scopeCheck = (k) => { if (req.dn.equals(k)) return true; const parent = ldap.parseDN(k).parent(); return (parent ? parent.equals(req.dn) : false); }; break; case 'sub': scopeCheck = (k) => { return (req.dn.equals(k) || req.dn.parentOf(k)); }; break; } const keys = Object.keys(db); for (const key of keys) { if (!scopeCheck(key)) return; if (req.filter.matches(db[key])) { res.send({ dn: key, attributes: db[key] }); } } res.end(); return next(); }); ///--- Fire it up server.listen(1389, () => { console.log('LDAP server up at: %s', server.url); }); ``` # /etc/passwd server ```js const fs = require('fs'); const ldap = require('ldapjs'); const { spawn } = require('child_process'); ///--- Shared handlers function authorize(req, res, next) { if (!req.connection.ldap.bindDN.equals('cn=root')) return next(new ldap.InsufficientAccessRightsError()); return next(); } function loadPasswdFile(req, res, next) { fs.readFile('/etc/passwd', 'utf8', (err, data) => { if (err) return next(new ldap.OperationsError(err.message)); req.users = {}; const lines = data.split('\n'); for (const line of lines) { if (!line || /^#/.test(line)) continue; const record = line.split(':'); if (!record || !record.length) continue; req.users[record[0]] = { dn: 'cn=' + record[0] + ', ou=users, o=myhost', attributes: { cn: record[0], uid: record[2], gid: record[3], description: record[4], homedirectory: record[5], shell: record[6] || '', objectclass: 'unixUser' } }; } return next(); }); } const pre = [authorize, loadPasswdFile]; ///--- Mainline const server = ldap.createServer(); server.bind('cn=root', (req, res, next) => { if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret') return next(new ldap.InvalidCredentialsError()); res.end(); return next(); }); server.add('ou=users, o=myhost', pre, (req, res, next) => { if (!req.dn.rdns[0].cn) return next(new ldap.ConstraintViolationError('cn required')); if (req.users[req.dn.rdns[0].cn]) return next(new ldap.EntryAlreadyExistsError(req.dn.toString())); const entry = req.toObject().attributes; if (entry.objectclass.indexOf('unixUser') === -1) return next(new ldap.ConstraintViolationError('entry must be a unixUser')); const opts = ['-m']; if (entry.description) { opts.push('-c'); opts.push(entry.description[0]); } if (entry.homedirectory) { opts.push('-d'); opts.push(entry.homedirectory[0]); } if (entry.gid) { opts.push('-g'); opts.push(entry.gid[0]); } if (entry.shell) { opts.push('-s'); opts.push(entry.shell[0]); } if (entry.uid) { opts.push('-u'); opts.push(entry.uid[0]); } opts.push(entry.cn[0]); const useradd = spawn('useradd', opts); const messages = []; useradd.stdout.on('data', (data) => { messages.push(data.toString()); }); useradd.stderr.on('data', (data) => { messages.push(data.toString()); }); useradd.on('exit', (code) => { if (code !== 0) { let msg = '' + code; if (messages.length) msg += ': ' + messages.join(); return next(new ldap.OperationsError(msg)); } res.end(); return next(); }); }); server.modify('ou=users, o=myhost', pre, (req, res, next) => { if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn]) return next(new ldap.NoSuchObjectError(req.dn.toString())); if (!req.changes.length) return next(new ldap.ProtocolError('changes required')); const user = req.users[req.dn.rdns[0].cn].attributes; let mod; for (const change of req.changes) { mod = change.modification; switch (change.operation) { case 'replace': if (mod.type !== 'userpassword' || !mod.vals || !mod.vals.length) return next(new ldap.UnwillingToPerformError('only password updates ' + 'allowed')); break; case 'add': case 'delete': return next(new ldap.UnwillingToPerformError('only replace allowed')); } } const passwd = spawn('chpasswd', ['-c', 'MD5']); passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8'); passwd.on('exit', (code) => { if (code !== 0) return next(new ldap.OperationsError('' + code)); res.end(); return next(); }); }); server.del('ou=users, o=myhost', pre, (req, res, next) => { if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn]) return next(new ldap.NoSuchObjectError(req.dn.toString())); const userdel = spawn('userdel', ['-f', req.dn.rdns[0].cn]); const messages = []; userdel.stdout.on('data', (data) => { messages.push(data.toString()); }); userdel.stderr.on('data', (data) => { messages.push(data.toString()); }); userdel.on('exit', (code) => { if (code !== 0) { let msg = '' + code; if (messages.length) msg += ': ' + messages.join(); return next(new ldap.OperationsError(msg)); } res.end(); return next(); }); }); server.search('o=myhost', pre, (req, res, next) => { const keys = Object.keys(req.users); for (const k of keys) { if (req.filter.matches(req.users[k].attributes)) res.send(req.users[k]); } res.end(); return next(); }); // LDAP "standard" listens on 389, but whatever. server.listen(1389, '127.0.0.1', () => { console.log('/etc/passwd LDAP server up at: %s', server.url); }); ``` # Address Book This example is courtesy of [Diogo Resende](https://github.com/dresende) and illustrates setting up an address book for typical mail clients such as Thunderbird or Evolution over a MySQL database. ```js // MySQL test: (create on database 'abook' with username 'abook' and password 'abook') // // CREATE TABLE IF NOT EXISTS `users` ( // `id` int(5) unsigned NOT NULL AUTO_INCREMENT, // `username` varchar(50) NOT NULL, // `password` varchar(50) NOT NULL, // PRIMARY KEY (`id`), // KEY `username` (`username`) // ) ENGINE=InnoDB DEFAULT CHARSET=utf8; // INSERT INTO `users` (`username`, `password`) VALUES // ('demo', 'demo'); // CREATE TABLE IF NOT EXISTS `contacts` ( // `id` int(5) unsigned NOT NULL AUTO_INCREMENT, // `user_id` int(5) unsigned NOT NULL, // `name` varchar(100) NOT NULL, // `email` varchar(255) NOT NULL, // PRIMARY KEY (`id`), // KEY `user_id` (`user_id`) // ) ENGINE=InnoDB DEFAULT CHARSET=utf8; // INSERT INTO `contacts` (`user_id`, `name`, `email`) VALUES // (1, 'John Doe', 'john.doe@example.com'), // (1, 'Jane Doe', 'jane.doe@example.com'); // const ldap = require('ldapjs'); const mysql = require("mysql"); const server = ldap.createServer(); const addrbooks = {}; const userinfo = {}; const ldap_port = 389; const basedn = "dc=example, dc=com"; const company = "Example"; const db = mysql.createClient({ user: "abook", password: "abook", database: "abook" }); db.query("SELECT c.*,u.username,u.password " + "FROM contacts c JOIN users u ON c.user_id=u.id", (err, contacts) => { if (err) { console.log("Error fetching contacts", err); process.exit(1); } for (const contact of contacts) { if (!addrbooks.hasOwnProperty(contact.username)) { addrbooks[contact.username] = []; userinfo["cn=" + contact.username + ", " + basedn] = { abook: addrbooks[contact.username], pwd: contact.password }; } const p = contact.name.indexOf(" "); if (p != -1) contact.firstname = contact.name.substr(0, p); p = contact.name.lastIndexOf(" "); if (p != -1) contact.surname = contact.name.substr(p + 1); addrbooks[contact.username].push({ dn: "cn=" + contact.name + ", " + basedn, attributes: { objectclass: [ "top" ], cn: contact.name, mail: contact.email, givenname: contact.firstname, sn: contact.surname, ou: company } }); } server.bind(basedn, (req, res, next) => { const username = req.dn.toString(); const password = req.credentials; if (!userinfo.hasOwnProperty(username) || userinfo[username].pwd != password) { return next(new ldap.InvalidCredentialsError()); } res.end(); return next(); }); server.search(basedn, (req, res, next) => { const binddn = req.connection.ldap.bindDN.toString(); if (userinfo.hasOwnProperty(binddn)) { for (const abook of userinfo[binddn].abook) { if (req.filter.matches(abook.attributes)) res.send(abook); } } res.end(); }); server.listen(ldap_port, () => { console.log("Addressbook started at %s", server.url); }); }); ``` To test out this example, try: ```shell $ ldapsearch -H ldap://localhost:389 -x -D cn=demo,dc=example,dc=com \ -w demo -b "dc=example,dc=com" objectclass=* ``` # Multi-threaded Server This example demonstrates multi-threading via the `cluster` module utilizing a `net` server for initial socket receipt. An alternate example demonstrating use of the `connectionRouter` `serverOptions` hook is available in the `examples` directory. ```js const cluster = require('cluster'); const ldap = require('ldapjs'); const net = require('net'); const os = require('os'); const threads = []; threads.getNext = function () { return (Math.floor(Math.random() * this.length)); }; const serverOptions = { port: 1389 }; if (cluster.isMaster) { const server = net.createServer(serverOptions, (socket) => { socket.pause(); console.log('ldapjs client requesting connection'); let routeTo = threads.getNext(); threads[routeTo].send({ type: 'connection' }, socket); }); for (let i = 0; i < os.cpus().length; i++) { let thread = cluster.fork({ 'id': i }); thread.id = i; thread.on('message', function (msg) { }); threads.push(thread); } server.listen(serverOptions.port, function () { console.log('ldapjs listening at ldap://127.0.0.1:' + serverOptions.port); }); } else { const server = ldap.createServer(serverOptions); let threadId = process.env.id; process.on('message', (msg, socket) => { switch (msg.type) { case 'connection': server.newConnection(socket); socket.resume(); console.log('ldapjs client connection accepted on ' + threadId.toString()); } }); server.search('dc=example', function (req, res, next) { console.log('ldapjs search initiated on ' + threadId.toString()); var obj = { dn: req.dn.toString(), attributes: { objectclass: ['organization', 'top'], o: 'example' } }; if (req.filter.matches(obj.attributes)) res.send(obj); res.end(); }); } ``` node-ldapjs-2.3.3/docs/filters.md000066400000000000000000000212031424777304200166510ustar00rootroot00000000000000--- title: Filters API | ldapjs --- # ldapjs Filters API
This document covers the ldapjs filters API and assumes that you are familiar with LDAP. If you're not, read the [guide](guide.html) first.
LDAP search filters are really the backbone of LDAP search operations, and ldapjs tries to get you in "easy" with them if your dataset is small, and also lets you introspect them if you want to write a "query planner". For reference, make sure to read over [RFC2254](http://www.ietf.org/rfc/rfc2254.txt), as this explains the LDAPv3 text filter representation. ldapjs gives you a distinct object type mapping to each filter that is context-sensitive. However, _all_ filters have a `matches()` method on them, if that's all you need. Most filters will have an `attribute` property on them, since "simple" filters all operate on an attribute/value assertion. The "complex" filters are really aggregations of other filters (i.e. 'and'), and so these don't provide that property. All Filters in the ldapjs framework extend from `Filter`, which wil have the property `type` available; this will return a string name for the filter, and will be one of: # parseFilter(filterString) Parses an [RFC2254](http://www.ietf.org/rfc/rfc2254.txt) filter string into an ldapjs object(s). If the filter is "complex", it will be a "tree" of objects. For example: ```js const parseFilter = require('ldapjs').parseFilter; const f = parseFilter('(objectclass=*)'); ``` Is a "simple" filter, and would just return a `PresenceFilter` object. However, ```js const f = parseFilter('(&(employeeType=manager)(l=Seattle))'); ``` Would return an `AndFilter`, which would have a `filters` array of two `EqualityFilter` objects. `parseFilter` will throw if an invalid string is passed in (that is, a syntactically invalid string). # EqualityFilter The equality filter is used to check exact matching of attribute/value assertions. This object will have an `attribute` and `value` property, and the `name` property will be `equal`. The string syntax for an equality filter is `(attr=value)`. The `matches()` method will return true IFF the passed in object has a key matching `attribute` and a value matching `value`. ```js const f = new EqualityFilter({ attribute: 'cn', value: 'foo' }); f.matches({cn: 'foo'}); => true f.matches({cn: 'bar'}); => false ``` Equality matching uses "strict" type JavaScript comparison, and by default everything in ldapjs (and LDAP) is a UTF-8 string. If you want comparison of numbers, or something else, you'll need to use a middleware interceptor that transforms values of objects. # PresenceFilter The presence filter is used to check if an object has an attribute at all, with any value. This object will have an `attribute` property, and the `name` property will be `present`. The string syntax for a presence filter is `(attr=*)`. The `matches()` method will return true IFF the passed in object has a key matching `attribute`. ```js const f = new PresenceFilter({ attribute: 'cn' }); f.matches({cn: 'foo'}); => true f.matches({sn: 'foo'}); => false ``` # SubstringFilter The substring filter is used to do wildcard matching of a string value. This object will have an `attribute` property and then it will have an `initial` property, which is the prefix match, an `any` which will be an array of strings that are to be found _somewhere_ in the target string, and a `final` property, which will be the suffix match of the string. `any` and `final` are both optional. The `name` property will be `substring`. The string syntax for a presence filter is `(attr=foo*bar*cat*dog)`, which would map to: ```js { initial: 'foo', any: ['bar', 'cat'], final: 'dog' } ``` The `matches()` method will return true IFF the passed in object has a key matching `attribute` and the "regex" matches the value ```js const f = new SubstringFilter({ attribute: 'cn', initial: 'foo', any: ['bar'], final: 'baz' }); f.matches({cn: 'foobigbardogbaz'}); => true f.matches({sn: 'fobigbardogbaz'}); => false ``` # GreaterThanEqualsFilter The ge filter is used to do comparisons and ordering based on the value type. As mentioned elsewhere, by default everything in LDAP and ldapjs is a string, so this filter's `matches()` would be using lexicographical ordering of strings. If you wanted `>=` semantics over numeric values, you would need to add some middleware to convert values before comparison (and the value of the filter). Note that the ldapjs schema middleware will do this. The GreaterThanEqualsFilter will have an `attribute` property, a `value` property and the `name` property will be `ge`. The string syntax for a ge filter is: ``` (cn>=foo) ``` The `matches()` method will return true IFF the passed in object has a key matching `attribute` and the value is `>=` this filter's `value`. ```js const f = new GreaterThanEqualsFilter({ attribute: 'cn', value: 'foo', }); f.matches({cn: 'foobar'}); => true f.matches({cn: 'abc'}); => false ``` # LessThanEqualsFilter The le filter is used to do comparisons and ordering based on the value type. As mentioned elsewhere, by default everything in LDAP and ldapjs is a string, so this filter's `matches()` would be using lexicographical ordering of strings. If you wanted `<=` semantics over numeric values, you would need to add some middleware to convert values before comparison (and the value of the filter). Note that the ldapjs schema middleware will do this. The string syntax for a le filter is: ``` (cn<=foo) ``` The LessThanEqualsFilter will have an `attribute` property, a `value` property and the `name` property will be `le`. The `matches()` method will return true IFF the passed in object has a key matching `attribute` and the value is `<=` this filter's `value`. ```js const f = new LessThanEqualsFilter({ attribute: 'cn', value: 'foo', }); f.matches({cn: 'abc'}); => true f.matches({cn: 'foobar'}); => false ``` # AndFilter The and filter is a complex filter that simply contains "child" filters. The object will have a `filters` property which is an array of `Filter` objects. The `name` property will be `and`. The string syntax for an and filter is (assuming below we're and'ing two equality filters): ``` (&(cn=foo)(sn=bar)) ``` The `matches()` method will return true IFF the passed in object matches all the filters in the `filters` array. ```js const f = new AndFilter({ filters: [ new EqualityFilter({ attribute: 'cn', value: 'foo' }), new EqualityFilter({ attribute: 'sn', value: 'bar' }) ] }); f.matches({cn: 'foo', sn: 'bar'}); => true f.matches({cn: 'foo', sn: 'baz'}); => false ``` # OrFilter The or filter is a complex filter that simply contains "child" filters. The object will have a `filters` property which is an array of `Filter` objects. The `name` property will be `or`. The string syntax for an or filter is (assuming below we're or'ing two equality filters): ``` (|(cn=foo)(sn=bar)) ``` The `matches()` method will return true IFF the passed in object matches *any* of the filters in the `filters` array. ```js const f = new OrFilter({ filters: [ new EqualityFilter({ attribute: 'cn', value: 'foo' }), new EqualityFilter({ attribute: 'sn', value: 'bar' }) ] }); f.matches({cn: 'foo', sn: 'baz'}); => true f.matches({cn: 'bar', sn: 'baz'}); => false ``` # NotFilter The not filter is a complex filter that contains a single "child" filter. The object will have a `filter` property which is an instance of a `Filter` object. The `name` property will be `not`. The string syntax for a not filter is (assuming below we're not'ing an equality filter): ``` (!(cn=foo)) ``` The `matches()` method will return true IFF the passed in object does not match the filter in the `filter` property. ```js const f = new NotFilter({ filter: new EqualityFilter({ attribute: 'cn', value: 'foo' }) }); f.matches({cn: 'bar'}); => true f.matches({cn: 'foo'}); => false ``` # ApproximateFilter The approximate filter is used to check "approximate" matching of attribute/value assertions. This object will have an `attribute` and `value` property, and the `name` property will be `approx`. As a side point, this is a useless filter. It's really only here if you have some whacky client that's sending this. It just does an exact match (which is what ActiveDirectory does too). The string syntax for an equality filter is `(attr~=value)`. The `matches()` method will return true IFF the passed in object has a key matching `attribute` and a value exactly matching `value`. ```js const f = new ApproximateFilter({ attribute: 'cn', value: 'foo' }); f.matches({cn: 'foo'}); => true f.matches({cn: 'bar'}); => false ``` node-ldapjs-2.3.3/docs/guide.md000066400000000000000000000574371424777304200163200ustar00rootroot00000000000000--- title: LDAP Guide | ldapjs --- # LDAP Guide
This guide was written assuming that you (1) don't know anything about ldapjs, and perhaps more importantly (2) know little, if anything about LDAP. If you're already an LDAP whiz, please don't read this and feel it's condescending. Most people don't know how LDAP works, other than that "it's that thing that has my password." By the end of this guide, we'll have a simple LDAP server that accomplishes a "real" task.
# What exactly is LDAP? If you haven't already read the [wikipedia](http://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol) entry (which you should go do right now), LDAP is the "Lightweight Directory Access Protocol". A directory service basically breaks down as follows: * A directory is a tree of entries (similar to but different than an FS). * Every entry has a unique name in the tree. * An entry is a set of attributes. * An attribute is a key/value(s) pairing (multivalue is natural). It might be helpful to visualize: ``` o=example / \ ou=users ou=groups / | | \ cn=john cn=jane cn=dudes cn=dudettes / keyid=foo ``` Let's say we wanted to look at the record cn=john: ```shell dn: cn=john, ou=users, o=example cn: john sn: smith email: john@example.com email: john.smith@example.com objectClass: person ``` A few things to note: * All names in a directory tree are actually referred to as a _distinguished name_, or _dn_ for short. A dn is comprised of attributes that lead to that node in the tree, as shown above (the syntax is foo=bar, ...). * The root of the tree is at the right of the _dn_, which is inverted from a filesystem hierarchy. * Every entry in the tree is an _instance of_ an _objectclass_. * An _objectclass_ is a schema concept; think of it like a table in a traditional ORM. * An _objectclass_ defines what _attributes_ an entry can have (on the ORM analogy, an _attribute_ would be like a column). That's it. LDAP, then, is the protocol for interacting with the directory tree, and it's comprehensively specified for common operations, like add/update/delete and importantly, search. Really, the power of LDAP comes through the search operations defined in the protocol, which are richer than HTTP query string filtering, but less powerful than full SQL. You can think of LDAP as a NoSQL/document store with a well-defined query syntax. So, why isn't LDAP more popular for a lot of applications? Like anything else that has "simple" or "lightweight" in the name, it's not really that lightweight. In particular, almost all of the implementations of LDAP stem from the original University of Michigan codebase written in 1996. At that time, the original intention of LDAP was to be an IP-accessible gateway to the much more complex X.500 directories, which means that a lot of that baggage has carried through to today. That makes for a high barrier to entry, when most applications just don't need most of those features. ## How is ldapjs any different? Well, on the one hand, since ldapjs has to be 100% wire compatible with LDAP to be useful, it's not. On the other hand, there are no forced assumptions about what you need and don't need for your use of a directory system. For example, want to run with no-schema in OpenLDAP/389DS/et al? Good luck. Most of the server implementations support arbitrary "backends" for persistence, but really you'll be using [BDB](http://www.oracle.com/technetwork/database/berkeleydb/overview/index.html). Want to run schema-less in ldapjs, or wire it up with some mongoose models? No problem. Want to back it to redis? Should be able to get some basics up in a day or two. Basically, the ldapjs philosophy is to deal with the "muck" of LDAP, and then get out of the way so you can just use the "good parts." # Ok, cool. Learn me some LDAP! With the initial fluff out of the way, let's do something crazy to teach you some LDAP. Let's put an LDAP server up over the top of your (Linux) host's /etc/passwd and /etc/group files. Usually sysadmins "go the other way," and replace /etc/passwd with a [PAM](http://en.wikipedia.org/wiki/Pluggable_authentication_module "Pluggable authentication module") module to LDAP. While this is probably not a super useful real-world use case, it will teach you some of the basics. If it is useful to you, then that's gravy. ## Install If you don't already have node.js and npm, clearly you need those, so follow the steps at [nodejs.org](http://nodejs.org) and [npmjs.org](http://npmjs.org), respectively. After that, run: ```shell $ npm install ldapjs ``` Rather than overload you with client-side programming for now, we'll use the OpenLDAP CLI to interact with our server. It's almost certainly already installed on your system, but if not, you can get it from brew/apt/yum/your package manager here. To get started, open some file, and let's get the library loaded and a server created: ```js const ldap = require('ldapjs'); const server = ldap.createServer(); server.listen(1389, () => { console.log('/etc/passwd LDAP server up at: %s', server.url); }); ``` And run that. Doing anything will give you errors (LDAP "No Such Object") since we haven't added any support in yet, but go ahead and try it anyway: ```shell $ ldapsearch -H ldap://localhost:1389 -x -b "o=myhost" objectclass=* ``` Before we go any further, note that the complete code for the server we are about to build up is on the [examples](examples.html) page. ## Bind So, lesson #1 about LDAP: unlike HTTP, it's connection-oriented; that means that you authenticate (in LDAP nomenclature this is called a _bind_), and all subsequent operations operate at the level of priviledge you established during a bind. You can bind any number of times on a single connection and change that identity. Technically, it's optional, and you can support _anonymous_ operations from clients, but (1) you probably don't want that, and (2) most LDAP clients will initiate a bind anyway (OpenLDAP will), so let's add it in and get it out of our way. What we're going to do is add a "root" user to our LDAP server. This root user has no correspondence to our Unix root user, it's just something we're making up and going to use for allowing an (LDAP) admin to do anything. To do so, add this code into your file: ```js server.bind('cn=root', (req, res, next) => { if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret') return next(new ldap.InvalidCredentialsError()); res.end(); return next(); }); ``` Not very secure, but this is a demo. What we did there was "mount" a tree in the ldapjs server, and add a handler for the _bind_ method. If you've ever used express, this pattern should be really familiar; you can add any number of handlers in, as we'll see later. On to the meat of the method. What's up with this? ```js if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret') ``` The first part `req.dn.toString() !== 'cn=root'`: you're probably thinking "WTF?!? Does ldapjs allow something other than cn=root into this handler?" Sort of. It allows cn=root *and any children* into that handler. So the entries `cn=root` and `cn=evil, cn=root` would both match and flow into this handler. Hence that check. The second check `req.credentials` is probably obvious, but it brings up an important point, and that is the `req`, `res` objects in ldapjs are not homogenous across server operation types. Unlike HTTP, there's not a single message format, so each of the operations has fields and functions appropriate to that type. The LDAP bind operation has `credentials`, which are a string representation of the client's password. This is logically the same as HTTP Basic Authentication (there are other mechanisms, but that's out of scope for a getting started guide). Ok, if either of those checks failed, we pass a new ldapjs `Error` back into the server, and it will (1) halt the chain, and (2) send the proper error code back to the client. Lastly, assuming that this request was ok, we just end the operation with `res.end()`. The `return next()` isn't strictly necessary, since here we only have one handler in the chain, but it's good habit to always do that, so if you add another handler in later you won't get bit by it not being invoked. Blah blah, let's try running the ldap client again, first with a bad password: ```shell $ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w foo -b "o=myhost" objectclass=* ldap_bind: Invalid credentials (49) matched DN: cn=root additional info: Invalid Credentials ``` And again with the correct one: ```shell $ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" objectclass=* No such object (32) Additional information: No tree found for: o=myhost ``` Don't worry about all the flags we're passing into OpenLDAP, that's just to make their CLI less annonyingly noisy. This time, we got another `No such object` error, but it's for the tree `o=myhost`. That means our bind went through, and our search failed, since we haven't yet added a search handler. Just one more small thing to do first. Remember earlier I said there were no authorization rules baked into LDAP? Well, we added a bind route, so the only user that can authenticate is `cn=root`, but what if the remote end doesn't authenticate at all? Right, nothing says they *have to* bind, that's just what the common clients do. Let's add a quick authorization handler that we'll use in all our subsequent routes: ```js function authorize(req, res, next) { if (!req.connection.ldap.bindDN.equals('cn=root')) return next(new ldap.InsufficientAccessRightsError()); return next(); } ``` Should be pretty self-explanatory, but as a reminder, LDAP is connection oriented, so we check that the connection remote user was indeed our `cn=root` (by default ldapjs will have a DN of `cn=anonymous` if the client didn't bind). ## Search We said we wanted to allow LDAP operations over /etc/passwd, so let's detour for a moment to explain an /etc/passwd record. ```shell jsmith:x:1001:1000:Joe Smith,Room 1007,(234)555-8910,(234)555-0044,email:/home/jsmith:/bin/sh ``` The sample record above maps to: |Field |Description | |-------------------|-----------------------------------| |jsmith |Username | |x |Placeholder for password hash | |1001 |Numeric UID | |1000 |Numeric Primary GID | |'Joe Smith,...' |DisplayName | |/home/jsmith |Home directory | |/bin/sh |Shell | Let's write some handlers to parse that and transform it into an LDAP search record (note, you'll need to add `const fs = require('fs');` at the top of the source file). First, make a handler that just loads the "user database" in a "pre" handler: ```js function loadPasswdFile(req, res, next) { fs.readFile('/etc/passwd', 'utf8', (err, data) => { if (err) return next(new ldap.OperationsError(err.message)); req.users = {}; const lines = data.split('\n'); for (const line of lines) { if (!line || /^#/.test(line)) continue; const record = line.split(':'); if (!record || !record.length) continue; req.users[record[0]] = { dn: 'cn=' + record[0] + ', ou=users, o=myhost', attributes: { cn: record[0], uid: record[2], gid: record[3], description: record[4], homedirectory: record[5], shell: record[6] || '', objectclass: 'unixUser' } }; } return next(); }); } ``` Ok, all that did is tack the /etc/passwd records onto req.users so that any subsequent handler doesn't have to reload the file. Next, let's write a search handler to process that: ```js const pre = [authorize, loadPasswdFile]; server.search('o=myhost', pre, (req, res, next) => { const keys = Object.keys(req.users); for (const k of keys) { if (req.filter.matches(req.users[k].attributes)) res.send(req.users[k]); } res.end(); return next(); }); ``` And try running: ```shell $ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" cn=root dn: cn=root, ou=users, o=myhost cn: root uid: 0 gid: 0 description: System Administrator homedirectory: /var/root shell: /bin/sh objectclass: unixUser ``` Sweet! Try this out too: ```shell $ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" objectclass=* ... ``` You should have seen an entry for every record in /etc/passwd with the second. What all did we do here? A lot. Let's break this down... ### What did I just do on the command line? Let's start with looking at what you even asked for: ```shell $ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" cn=root ``` We can throw away `ldapsearch -H -x -D -w -LLL`, as those just specify the URL to connect to, the bind credentials and the `-LLL` just quiets down OpenLDAP. That leaves us with: `-b "o=myhost" cn=root`. The `-b o=myhost` tells our LDAP server where to _start_ looking in the tree for entries that might match the search filter, which above is `cn=root`. In this little LDAP example, we're mostly throwing out any qualification of the "tree," since there's not actually a tree in /etc/passwd (we will extend later with /etc/group). Remember how I said ldapjs gets out of the way and doesn't force anything on you? Here's an example. If we wanted an LDAP server to run over the filesystem, we actually would use this, but here, meh. Next, `cn=root` is the search "filter". LDAP has a rich specification of filters, where you can specify `and`, `or`, `not`, `>=`, `<=`, `equal`, `wildcard`, `present` and a few other esoteric things. Really, `equal`, `wildcard`, `present` and the boolean operators are all you'll likely ever need. So, the filter `cn=root` is an "equality" filter, and says to only return entries that have attributes that match that. In the second invocation, we used a 'presence' filter, to say 'return any entries that have an objectclass' attribute, which in LDAP parlance is saying "give me everything." ### The code In the code above, let's ignore the fs and split stuff, since really all we did was read in /etc/passwd line by line. After that, we looked at each record and made the cheesiest transform ever, which is making up a "search entry." A search entry _must_ have a DN so the client knows what record it is, and a set of attributes. So that's why we did this: ```js const entry = { dn: 'cn=' + record[0] + ', ou=users, o=myhost', attributes: { cn: record[0], uid: record[2], gid: record[3], description: record[4], homedirectory: record[5], shell: record[6] || '', objectclass: 'unixUser' } }; ``` Next, we let ldapjs do all the hard work of figuring out LDAP search filters for us by calling `req.filter.matches`. If it matched, we return the whole record with `res.send`. In this little example we're running O(n), so for something big and/or slow, you'd have to do some work to effectively write a query planner (or just not support it...). For some reference code, check out `node-ldapjs-riak`, which takes on the fairly difficult task of writing a 'full' LDAP server over riak. To demonstrate what ldapjs is doing for you, let's find all users who have a shell set to `/bin/false` and whose name starts with `p` (I'm doing this on Ubuntu). Then, let's say we only care about their login name and primary group id. We'd do this: ```shell $ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" "(&(shell=/bin/false)(cn=p*))" cn gid dn: cn=proxy, ou=users, o=myhost cn: proxy gid: 13 dn: cn=pulse, ou=users, o=myhost cn: pulse gid: 114 ``` ## Add This is going to be a little bit ghetto, since what we're going to do is just use node's child process module to spawn calls to `adduser`. Go ahead and add the following code in as another handler (you'll need a `const { spawn } = require('child_process');` at the top of your file): ```js server.add('ou=users, o=myhost', pre, (req, res, next) => { if (!req.dn.rdns[0].attrs.cn) return next(new ldap.ConstraintViolationError('cn required')); if (req.users[req.dn.rdns[0].attrs.cn.value]) return next(new ldap.EntryAlreadyExistsError(req.dn.toString())); const entry = req.toObject().attributes; if (entry.objectclass.indexOf('unixUser') === -1) return next(new ldap.ConstraintViolationError('entry must be a unixUser')); const opts = ['-m']; if (entry.description) { opts.push('-c'); opts.push(entry.description[0]); } if (entry.homedirectory) { opts.push('-d'); opts.push(entry.homedirectory[0]); } if (entry.gid) { opts.push('-g'); opts.push(entry.gid[0]); } if (entry.shell) { opts.push('-s'); opts.push(entry.shell[0]); } if (entry.uid) { opts.push('-u'); opts.push(entry.uid[0]); } opts.push(entry.cn[0]); const useradd = spawn('useradd', opts); const messages = []; useradd.stdout.on('data', (data) => { messages.push(data.toString()); }); useradd.stderr.on('data', (data) => { messages.push(data.toString()); }); useradd.on('exit', (code) => { if (code !== 0) { let msg = '' + code; if (messages.length) msg += ': ' + messages.join(); return next(new ldap.OperationsError(msg)); } res.end(); return next(); }); }); ``` Then, you'll need to be root to have this running, so start your server with `sudo` (or be root, whatever). Now, go ahead and create a file called `user.ldif` with the following contents: ```shell dn: cn=ldapjs, ou=users, o=myhost objectClass: unixUser cn: ldapjs shell: /bin/bash description: Created via ldapadd ``` Now go ahead and invoke with: ```shell $ ldapadd -H ldap://localhost:1389 -x -D cn=root -w secret -f ./user.ldif adding new entry "cn=ldapjs, ou=users, o=myhost" ``` Let's confirm he got added with an ldapsearch: ```shell $ ldapsearch -H ldap://localhost:1389 -LLL -x -D cn=root -w secret -b "ou=users, o=myhost" cn=ldapjs dn: cn=ldapjs, ou=users, o=myhost cn: ldapjs uid: 1001 gid: 1001 description: Created via ldapadd homedirectory: /home/ldapjs shell: /bin/bash objectclass: unixUser ``` As before, here's a breakdown of the code: ```js server.add('ou=users, o=myhost', pre, (req, res, next) => { if (!req.dn.rdns[0].attrs.cn) return next(new ldap.ConstraintViolationError('cn required')); if (req.users[req.dn.rdns[0].attrs.cn.value]) return next(new ldap.EntryAlreadyExistsError(req.dn.toString())); const entry = req.toObject().attributes; if (entry.objectclass.indexOf('unixUser') === -1) return next(new ldap.ConstraintViolationError('entry must be a unixUser')); }); ``` A few new things: * We mounted this handler at `ou=users, o=myhost`. Why? What if we want to extend this little project with groups? We probably want those under a different part of the tree. * We did some really minimal schema enforcement by: + Checking that the leaf RDN (relative distinguished name) was a _cn_ attribute. + We then did `req.toObject()`. As mentioned before, each of the req/res objects have special APIs that make sense for that operation. Without getting into the details, the LDAP add operation on the wire doesn't look like a JS object, and we want to support both the LDAP nerd that wants to see what got sent, and the "easy" case. So use `.toObject()`. Note we also filtered out to the `attributes` portion of the object since that's all we're really looking at. + Lastly, we did a super minimal check to see if the entry was of type `unixUser`. Frankly for this case, it's kind of useless, but it does illustrate one point: attribute names are case-insensitive, so ldapjs converts them all to lower case (note the client sent _objectClass_ over the wire). After that, we really just delegated off to the _useradd_ command. As far as I know, there is not a node.js module that wraps up `getpwent` and friends, otherwise we'd use that. Now, what's missing? Oh, right, we need to let you set a password. Well, let's support that via the _modify_ command. ## Modify Unlike HTTP, "partial" document updates are fully specified as part of the RFC, so appending, removing, or replacing a single attribute is pretty natural. Go ahead and add the following code into your source file: ```js server.modify('ou=users, o=myhost', pre, (req, res, next) => { if (!req.dn.rdns[0].attrs.cn || !req.users[req.dn.rdns[0].attrs.cn.value]) return next(new ldap.NoSuchObjectError(req.dn.toString())); if (!req.changes.length) return next(new ldap.ProtocolError('changes required')); const user = req.users[req.dn.rdns[0].attrs.cn.value].attributes; let mod; for (const i = 0; i < req.changes.length; i++) { mod = req.changes[i].modification; switch (req.changes[i].operation) { case 'replace': if (mod.type !== 'userpassword' || !mod.vals || !mod.vals.length) return next(new ldap.UnwillingToPerformError('only password updates ' + 'allowed')); break; case 'add': case 'delete': return next(new ldap.UnwillingToPerformError('only replace allowed')); } } const passwd = spawn('chpasswd', ['-c', 'MD5']); passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8'); passwd.on('exit', (code) => { if (code !== 0) return next(new ldap.OperationsError(code)); res.end(); return next(); }); }); ``` Basically, we made sure the remote client was targeting an entry that exists, ensuring that they were asking to "replace" the `userPassword` attribute (which is the 'standard' LDAP attribute for passwords; if you think it's easier to use 'password', knock yourself out), and then just delegating to the `chpasswd` command (which lets you change a user's password over stdin). Next, go ahead and create a `passwd.ldif` file: ```shell dn: cn=ldapjs, ou=users, o=myhost changetype: modify replace: userPassword userPassword: secret - ``` And then run the OpenLDAP CLI: ```shell $ ldapmodify -H ldap://localhost:1389 -x -D cn=root -w secret -f ./passwd.ldif ``` You should now be able to login to your box as the ldapjs user. Let's get the last "mainline" piece of work out of the way, and delete the user. ## Delete Delete is pretty straightforward. The client gives you a dn to delete, and you delete it :). Add the following code into your server: ```js server.del('ou=users, o=myhost', pre, (req, res, next) => { if (!req.dn.rdns[0].attrs.cn || !req.users[req.dn.rdns[0].attrs.cn.value]) return next(new ldap.NoSuchObjectError(req.dn.toString())); const userdel = spawn('userdel', ['-f', req.dn.rdns[0].attrs.cn.value]); const messages = []; userdel.stdout.on('data', (data) => { messages.push(data.toString()); }); userdel.stderr.on('data', (data) => { messages.push(data.toString()); }); userdel.on('exit', (code) => { if (code !== 0) { let msg = '' + code; if (messages.length) msg += ': ' + messages.join(); return next(new ldap.OperationsError(msg)); } res.end(); return next(); }); }); ``` And then run the following command: ```shell $ ldapdelete -H ldap://localhost:1389 -x -D cn=root -w secret "cn=ldapjs, ou=users, o=myhost" ``` # Where to go from here The complete source code for this example server is available in [examples](examples.html). Make sure to read up on the [server](server.html) and [client](client.html) APIs. If you're looking for a "drop in" solution, take a look at [ldapjs-riak](https://github.com/mcavage/node-ldapjs-riak). [Mozilla](https://wiki.mozilla.org/Mozilla_LDAP_SDK_Programmer%27s_Guide/Understanding_LDAP) still maintains some web pages with LDAP overviews if you look around, if you're looking for more tutorials. After that, you'll need to work your way through the [RFCs](http://tools.ietf.org/html/rfc4510) as you work through the APIs in ldapjs. node-ldapjs-2.3.3/docs/index.md000066400000000000000000000052611424777304200163160ustar00rootroot00000000000000--- title: ldapjs ---
Reimagining LDAP for Node.js
# Overview
ldapjs is a pure JavaScript, from-scratch framework for implementing [LDAP](http://tools.ietf.org/html/rfc4510) clients and servers in [Node.js](http://nodejs.org). It is intended for developers used to interacting with HTTP services in node and [restify](http://restify.com).
```js const ldap = require('ldapjs'); const server = ldap.createServer(); server.search('o=example', (req, res, next) => { const obj = { dn: req.dn.toString(), attributes: { objectclass: ['organization', 'top'], o: 'example' } }; if (req.filter.matches(obj.attributes)) res.send(obj); res.end(); }); server.listen(1389, () => { console.log('LDAP server listening at %s', server.url); }); ``` Try hitting that with: ```shell $ ldapsearch -H ldap://localhost:1389 -x -b o=example objectclass=* ``` # Features ldapjs implements most of the common operations in the LDAP v3 RFC(s), for both client and server. It is 100% wire-compatible with the LDAP protocol itself, and is interoperable with [OpenLDAP](http://openldap.org) and any other LDAPv3-compliant implementation. ldapjs gives you a powerful routing and "intercepting filter" pattern for implementing server(s). It is intended that you can build LDAP over anything you want, not just traditional databases. # Getting started ```shell $ npm install ldapjs ``` If you're new to LDAP, check out the [guide](guide.html). Otherwise, the API documentation is: |Section |Content | |---------------------------|-------------------------------------------| |[Server API](server.html) |Reference for implementing LDAP servers. | |[Client API](client.html) |Reference for implementing LDAP clients. | |[DN API](dn.html) |API reference for the DN class. | |[Filter API](filters.html) |API reference for LDAP search filters. | |[Error API](errors.html) |Listing of all ldapjs Error objects. | |[Examples](examples.html) |Collection of sample/getting started code. | # More information - License:[MIT](http://opensource.org/licenses/mit-license.php) - Code: [ldapjs/node-ldapjs](https://github.com/ldapjs/node-ldapjs) # What's not in the box? Since most developers and system(s) adminstrators struggle with some of the esoteric features of LDAP, not all features in LDAP are implemented here. Specifically: * LDIF * Aliases * Attributes by OID * Extensible matching There are a few others, but those are the "big" ones. node-ldapjs-2.3.3/docs/server.md000066400000000000000000000374201424777304200165170ustar00rootroot00000000000000--- title: Server API | ldapjs --- # ldapjs Server API
This document covers the ldapjs server API and assumes that you are familiar with LDAP. If you're not, read the [guide](guide.html) first.
# Create a server The code to create a new server looks like: ```js const server = ldap.createServer(); ``` The full list of options is: ||log||You can optionally pass in a Bunyan compatible logger instance the client will use to acquire a child logger.|| ||certificate||A PEM-encoded X.509 certificate; will cause this server to run in TLS mode.|| ||key||A PEM-encoded private key that corresponds to _certificate_ for SSL.|| ### Note On Logger The passed in logger is expected to conform to the Log4j standard API. Internally, [abstract-logging](https://www.npmjs.com/packages/abstract-logging) is used to implement the interface. As a result, no log messages will be generated unless an external logger is supplied. Known compatible loggers are: + [Bunyan](https://www.npmjs.com/package/bunyan) + [Pino](https://www.npmjs.com/package/pino) ## Properties on the server object ### maxConnections Set this property to reject connections when the server's connection count gets high. ### connections (getter only) - DEPRECATED The number of concurrent connections on the server. This property is deprecated, please use server.getConnections() instead. ### url Returns the fully qualified URL this server is listening on. For example: `ldaps://10.1.2.3:1636`. If you haven't yet called `listen`, it will always return `ldap://localhost:389`. ### Event: 'close' `function() {}` Emitted when the server closes. ## Listening for requests The LDAP server API wraps up and mirrors the node.js `server.listen` family of APIs. After calling `listen`, the property `url` on the server object itself will be available. Example: ```js server.listen(389, '127.0.0.1', function() { console.log('LDAP server listening at: ' + server.url); }); ``` ### Port and Host `listen(port, [host], [callback])` Begin accepting connections on the specified port and host. If the host is omitted, the server will accept connections directed to any IPv4 address (INADDR\_ANY). This function is asynchronous. The last parameter callback will be called when the server has been bound. ### Unix Domain Socket `listen(path, [callback])` Start a UNIX socket server listening for connections on the given path. This function is asynchronous. The last parameter callback will be called when the server has been bound. ### File descriptor `listenFD(fd)` Start a server listening for connections on the given file descriptor. This file descriptor must have already had the `bind(2)` and `listen(2)` system calls invoked on it. Additionally, it must be set non-blocking; try `fcntl(fd, F_SETFL, O_NONBLOCK)`. ## Inspecting server state ### server.getConnections(callback) The LDAP server API mirrors the [Node.js `server.getConnections` API](https://nodejs.org/dist/latest-v12.x/docs/api/net.html#net_server_getconnections_callback). Callback should take two arguments err and count. # Routes The LDAP server API is meant to be the LDAP-equivalent of the express/restify paradigm of programming. Essentially every method is of the form `OP(req, res, next)` where OP is one of bind, add, del, etc. You can chain handlers together by calling `next()` and ordering your functions in the definition of the route. For example: ```js function authorize(req, res, next) { if (!req.connection.ldap.bindDN.equals('cn=root')) return next(new ldap.InsufficientAccessRightsError()); return next(); } server.search('o=example', authorize, function(req, res, next) { ... }); ``` Note that ldapjs is also slightly different, since it's often going to be backed to a DB-like entity, in that it also has an API where you can pass in a 'backend' object. This is necessary if there are persistent connection pools, caching, etc. that need to be placed in an object. For example [ldapjs-riak](https://github.com/mcavage/node-ldapjs-riak) is a complete implementation of the LDAP protocol over [Riak](https://github.com/basho/riak). Getting an LDAP server up with riak looks like: ```js const ldap = require('ldapjs'); const ldapRiak = require('ldapjs-riak'); const server = ldap.createServer(); const backend = ldapRiak.createBackend({ "host": "localhost", "port": 8098, "bucket": "example", "indexes": ["l", "cn"], "uniqueIndexes": ["uid"], "numConnections": 5 }); server.add("o=example", backend, backend.add()); ... ``` The first parameter to an ldapjs route is always the point in the tree to mount the handler chain at. The second argument is _optionally_ a backend object. After that you can pass in an arbitrary combination of functions in the form `f(req, res, next)` or arrays of functions of the same signature (ldapjs will unroll them). Unlike HTTP, LDAP operations do not have a heterogeneous wire format, so each operation requires specific methods/fields on the request/response objects. However, there is a `.use()` method availabe, similar to that on express/connect, allowing you to chain up "middleware": ```js server.use(function(req, res, next) { console.log('hello world'); return next(); }); ``` ## Common Request Elements All request objects have the `dn` getter on it, which is "context-sensitive" and returns the point in the tree that the operation wants to operate on. The LDAP protocol itself sadly doesn't define operations this way, and has a unique name for just about every op. So, ldapjs calls it `dn`. The DN object itself is documented at [DN](dn.html). All requests have an optional array of `Control` objects. `Control` will have the properties `type` (string), `criticality` (boolean), and optionally, a string `value`. All request objects will have a `connection` object, which is the `net.Socket` associated to this request. Off the `connection` object is an `ldap` object. The most important property to pay attention to is the `bindDN` property which will be an instance of an `ldap.DN` object. This is what the client authenticated as on this connection. If the client didn't bind, then a DN object will be there defaulted to `cn=anonymous`. Additionally, request will have a `logId` parameter you can use to uniquely identify the request/connection pair in logs (includes the LDAP messageID). ## Common Response Elements All response objects will have an `end` method on them. By default, calling `res.end()` with no arguments will return SUCCESS (0x00) to the client (with the exception of `compare` which will return COMPARE\_TRUE (0x06)). You can pass in a status code to the `end()` method to return an alternate status code. However, it's more common/easier to use the `return next(new LDAPError())` pattern, since ldapjs will fill in the extra LDAPResult fields like matchedDN and error message for you. ## Errors ldapjs includes an exception hierarchy that directly corresponds to the RFC list of error codes. The complete list is documented in [errors](errors.html). But the paradigm is something defined like CONSTRAINT\_VIOLATION in the RFC would be `ConstraintViolationError` in ldapjs. Upon calling `next(new LDAPError())`, ldapjs will _stop_ calling your handler chain. For example: ```js server.search('o=example', (req, res, next) => { return next(); }, (req, res, next) => { return next(new ldap.OperationsError()); }, (req, res, next) => { res.end(); } ); ``` In the code snipped above, the third handler would never get invoked. # Bind Adds a mount in the tree to perform LDAP binds with. Example: ```js server.bind('ou=people, o=example', (req, res, next) => { console.log('bind DN: ' + req.dn.toString()); console.log('bind PW: ' + req.credentials); res.end(); }); ``` ## BindRequest BindRequest objects have the following properties: ### version The LDAP protocol version the client is requesting to run this connection on. Note that ldapjs only supports LDAP version 3. ### name The DN the client is attempting to bind as (note this is the same as the `dn` property). ### authentication The method of authentication. Right now only `simple` is supported. ### credentials The credentials to go with the `name/authentication` pair. For `simple`, this will be the plain-text password. ## BindResponse No extra methods above an `LDAPResult` API call. # Add Adds a mount in the tree to perform LDAP adds with. ```js server.add('ou=people, o=example', (req, res, next) => { console.log('DN: ' + req.dn.toString()); console.log('Entry attributes: ' + req.toObject().attributes); res.end(); }); ``` ## AddRequest AddRequest objects have the following properties: ### entry The DN the client is attempting to add (this is the same as the `dn` property). ### attributes The set of attributes in this entry. This will be an array of `Attribute` objects (which have a type and an array of values). This directly maps to how the request came in off the wire. It's likely you'll want to use `toObject()` and iterate that way, since that will transform an AddRequest into a standard JavaScript object. ### toObject() This operation will return a plain JavaScript object from the request that looks like: ```js { dn: 'cn=foo, o=example', // string, not DN object attributes: { cn: ['foo'], sn: ['bar'], objectclass: ['person', 'top'] } } ``` ## AddResponse No extra methods above an `LDAPResult` API call. # Search Adds a handler for the LDAP search operation. ```js server.search('o=example', (req, res, next) => { console.log('base object: ' + req.dn.toString()); console.log('scope: ' + req.scope); console.log('filter: ' + req.filter.toString()); res.end(); }); ``` ## SearchRequest SearchRequest objects have the following properties: ### baseObject The DN the client is attempting to start the search at (equivalent to `dn`). ### scope (string) one of: * base * one * sub ### derefAliases An integer (defined in the LDAP protocol). Defaults to '0' (meaning never deref). ### sizeLimit The number of entries to return. Defaults to '0' (unlimited). ldapjs doesn't currently automatically enforce this, but probably will at some point. ### timeLimit Maximum amount of time the server should take in sending search entries. Defaults to '0' (unlimited). ### typesOnly Whether to return only the names of attributes, and not the values. Defaults to 'false'. ldapjs will take care of this for you. ### filter The [filter](filters.html) object that the client requested. Notably this has a `matches()` method on it that you can leverage. For an example of introspecting a filter, take a look at the ldapjs-riak source. ### attributes An optional list of attributes to restrict the returned result sets to. ldapjs will automatically handle this for you. ## SearchResponse ### send(entry) Allows you to send a `SearchEntry` object. You do not need to explicitly pass in a `SearchEntry` object, and can instead just send a plain JavaScript object that matches the format used from `AddRequest.toObject()`. ```js server.search('o=example', (req, res, next) => { const obj = { dn: 'o=example', attributes: { objectclass: ['top', 'organization'], o: ['example'] } }; if (req.filter.matches(obj)) res.send(obj) res.end(); }); ``` # modify Allows you to handle an LDAP modify operation. ```js server.modify('o=example', (req, res, next) => { console.log('DN: ' + req.dn.toString()); console.log('changes:'); for (const c of req.changes) { console.log(' operation: ' + c.operation); console.log(' modification: ' + c.modification.toString()); } res.end(); }); ``` ## ModifyRequest ModifyRequest objects have the following properties: ### object The DN the client is attempting to update (this is the same as the `dn` property). ### changes An array of `Change` objects the client is attempting to perform. See below for details on the `Change` object. ## Change The `Change` object will have the following properties: ### operation A string, and will be one of: 'add', 'delete', or 'replace'. ### modification Will be an `Attribute` object, which will have a 'type' (string) field, and 'vals', which will be an array of string values. ## ModifyResponse No extra methods above an `LDAPResult` API call. # del Allows you to handle an LDAP delete operation. ```js server.del('o=example', (req, res, next) => { console.log('DN: ' + req.dn.toString()); res.end(); }); ``` ## DeleteRequest ### entry The DN the client is attempting to delete (this is the same as the `dn` property). ## DeleteResponse No extra methods above an `LDAPResult` API call. # compare Allows you to handle an LDAP compare operation. ```js server.compare('o=example', (req, res, next) => { console.log('DN: ' + req.dn.toString()); console.log('attribute name: ' + req.attribute); console.log('attribute value: ' + req.value); res.end(req.value === 'foo'); }); ``` ## CompareRequest ### entry The DN the client is attempting to compare (this is the same as the `dn` property). ### attribute The string name of the attribute to compare values of. ### value The string value of the attribute to compare. ## CompareResponse The `end()` method for compare takes a boolean, as opposed to a numeric code (you can still pass in a numeric LDAP status code if you want). Beyond that, there are no extra methods above an `LDAPResult` API call. # modifyDN Allows you to handle an LDAP modifyDN operation. ```js server.modifyDN('o=example', (req, res, next) => { console.log('DN: ' + req.dn.toString()); console.log('new RDN: ' + req.newRdn.toString()); console.log('deleteOldRDN: ' + req.deleteOldRdn); console.log('new superior: ' + (req.newSuperior ? req.newSuperior.toString() : '')); res.end(); }); ``` ## ModifyDNRequest ### entry The DN the client is attempting to rename (this is the same as the `dn` property). ### newRdn The leaf RDN the client wants to rename this entry to. This will be a DN object. ### deleteOldRdn Whether or not to delete the old RDN (i.e., rename vs copy). Defaults to 'true'. ### newSuperior Optional (DN). If the modifyDN operation wishes to relocate the entry in the tree, the `newSuperior` field will contain the new parent. ## ModifyDNResponse No extra methods above an `LDAPResult` API call. # exop Allows you to handle an LDAP extended operation. Extended operations are pretty much arbitrary extensions, by definition. Typically the extended 'name' is an OID, but ldapjs makes no such restrictions; it just needs to be a string. Unlike the other operations, extended operations don't map to any location in the tree, so routing here will be exact match, as opposed to subtree. ```js // LDAP whoami server.exop('1.3.6.1.4.1.4203.1.11.3', (req, res, next) => { console.log('name: ' + req.name); console.log('value: ' + req.value); res.value = 'u:xxyyz@EXAMPLE.NET'; res.end(); return next(); }); ``` ## ExtendedRequest ### name Will always be a match to the route-defined name. Clients must include this in their requests. ### value Optional string. The arbitrary blob the client sends for this extended operation. ## ExtendedResponse ### name The name of the extended operation. ldapjs will automatically set this. ### value The arbitrary (string) value to send back as part of the response. # unbind ldapjs by default provides an unbind handler that just disconnects the client and cleans up any internals (in ldapjs core). You can override this handler if you need to clean up any items in your backend, or perform any other cleanup tasks you need to. ```js server.unbind((req, res, next) => { res.end(); }); ``` Note that the LDAP unbind operation actually doesn't send any response (by definition in the RFC), so the UnbindResponse is really just a stub that ultimately calls `net.Socket.end()` for you. There are no properties available on either the request or response objects, except, of course, for `end()` on the response. node-ldapjs-2.3.3/examples/000077500000000000000000000000001424777304200155475ustar00rootroot00000000000000node-ldapjs-2.3.3/examples/cluster-threading-net-server.js000066400000000000000000000030201424777304200236140ustar00rootroot00000000000000const cluster = require('cluster') const ldap = require('ldapjs') const net = require('net') const os = require('os') const threads = [] threads.getNext = function () { return (Math.floor(Math.random() * this.length)) } const serverOptions = { port: 1389 } if (cluster.isMaster) { const server = net.createServer(serverOptions, (socket) => { socket.pause() console.log('ldapjs client requesting connection') const routeTo = threads.getNext() threads[routeTo].send({ type: 'connection' }, socket) }) for (let i = 0; i < os.cpus().length; i++) { const thread = cluster.fork({ id: i }) thread.id = i thread.on('message', function () { }) threads.push(thread) } server.listen(serverOptions.port, function () { console.log('ldapjs listening at ldap://127.0.0.1:' + serverOptions.port) }) } else { const server = ldap.createServer(serverOptions) const threadId = process.env.id process.on('message', (msg, socket) => { switch (msg.type) { case 'connection': server.newConnection(socket) socket.resume() console.log('ldapjs client connection accepted on ' + threadId.toString()) } }) server.search('dc=example', function (req, res) { console.log('ldapjs search initiated on ' + threadId.toString()) const obj = { dn: req.dn.toString(), attributes: { objectclass: ['organization', 'top'], o: 'example' } } if (req.filter.matches(obj.attributes)) { res.send(obj) } res.end() }) } node-ldapjs-2.3.3/examples/cluster-threading.js000066400000000000000000000027641424777304200215420ustar00rootroot00000000000000const cluster = require('cluster') const ldap = require('ldapjs') const os = require('os') const threads = [] threads.getNext = function () { return (Math.floor(Math.random() * this.length)) } const serverOptions = { connectionRouter: (socket) => { socket.pause() console.log('ldapjs client requesting connection') const routeTo = threads.getNext() threads[routeTo].send({ type: 'connection' }, socket) } } const server = ldap.createServer(serverOptions) if (cluster.isMaster) { for (let i = 0; i < os.cpus().length; i++) { const thread = cluster.fork({ id: i }) thread.id = i thread.on('message', function () { }) threads.push(thread) } server.listen(1389, function () { console.log('ldapjs listening at ' + server.url) }) } else { const threadId = process.env.id serverOptions.connectionRouter = () => { console.log('should not be hit') } process.on('message', (msg, socket) => { switch (msg.type) { case 'connection': server.newConnection(socket) socket.resume() console.log('ldapjs client connection accepted on ' + threadId.toString()) } }) server.search('dc=example', function (req, res) { console.log('ldapjs search initiated on ' + threadId.toString()) const obj = { dn: req.dn.toString(), attributes: { objectclass: ['organization', 'top'], o: 'example' } } if (req.filter.matches(obj.attributes)) { res.send(obj) } res.end() }) } node-ldapjs-2.3.3/examples/inmemory.js000066400000000000000000000100711424777304200177430ustar00rootroot00000000000000const ldap = require('../lib/index') /// --- Shared handlers function authorize (req, res, next) { /* Any user may search after bind, only cn=root has full power */ const isSearch = (req instanceof ldap.SearchRequest) if (!req.connection.ldap.bindDN.equals('cn=root') && !isSearch) { return next(new ldap.InsufficientAccessRightsError()) } return next() } /// --- Globals const SUFFIX = 'o=smartdc' const db = {} const server = ldap.createServer() server.bind('cn=root', function (req, res, next) { if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret') { return next(new ldap.InvalidCredentialsError()) } res.end() return next() }) server.add(SUFFIX, authorize, function (req, res, next) { const dn = req.dn.toString() if (db[dn]) { return next(new ldap.EntryAlreadyExistsError(dn)) } db[dn] = req.toObject().attributes res.end() return next() }) server.bind(SUFFIX, function (req, res, next) { const dn = req.dn.toString() if (!db[dn]) { return next(new ldap.NoSuchObjectError(dn)) } if (!db[dn].userpassword) { return next(new ldap.NoSuchAttributeError('userPassword')) } if (db[dn].userpassword.indexOf(req.credentials) === -1) { return next(new ldap.InvalidCredentialsError()) } res.end() return next() }) server.compare(SUFFIX, authorize, function (req, res, next) { const dn = req.dn.toString() if (!db[dn]) { return next(new ldap.NoSuchObjectError(dn)) } if (!db[dn][req.attribute]) { return next(new ldap.NoSuchAttributeError(req.attribute)) } let matches = false const vals = db[dn][req.attribute] for (let i = 0; i < vals.length; i++) { if (vals[i] === req.value) { matches = true break } } res.end(matches) return next() }) server.del(SUFFIX, authorize, function (req, res, next) { const dn = req.dn.toString() if (!db[dn]) { return next(new ldap.NoSuchObjectError(dn)) } delete db[dn] res.end() return next() }) server.modify(SUFFIX, authorize, function (req, res, next) { const dn = req.dn.toString() if (!req.changes.length) { return next(new ldap.ProtocolError('changes required')) } if (!db[dn]) { return next(new ldap.NoSuchObjectError(dn)) } const entry = db[dn] let mod for (let i = 0; i < req.changes.length; i++) { mod = req.changes[i].modification switch (req.changes[i].operation) { case 'replace': if (!entry[mod.type]) { return next(new ldap.NoSuchAttributeError(mod.type)) } if (!mod.vals || !mod.vals.length) { delete entry[mod.type] } else { entry[mod.type] = mod.vals } break case 'add': if (!entry[mod.type]) { entry[mod.type] = mod.vals } else { mod.vals.forEach(function (v) { if (entry[mod.type].indexOf(v) === -1) { entry[mod.type].push(v) } }) } break case 'delete': if (!entry[mod.type]) { return next(new ldap.NoSuchAttributeError(mod.type)) } delete entry[mod.type] break } } res.end() return next() }) server.search(SUFFIX, authorize, function (req, res, next) { const dn = req.dn.toString() if (!db[dn]) { return next(new ldap.NoSuchObjectError(dn)) } let scopeCheck switch (req.scope) { case 'base': if (req.filter.matches(db[dn])) { res.send({ dn: dn, attributes: db[dn] }) } res.end() return next() case 'one': scopeCheck = function (k) { if (req.dn.equals(k)) { return true } const parent = ldap.parseDN(k).parent() return (parent ? parent.equals(req.dn) : false) } break case 'sub': scopeCheck = function (k) { return (req.dn.equals(k) || req.dn.parentOf(k)) } break } Object.keys(db).forEach(function (key) { if (!scopeCheck(key)) { return } if (req.filter.matches(db[key])) { res.send({ dn: key, attributes: db[key] }) } }) res.end() return next() }) /// --- Fire it up server.listen(1389, function () { console.log('LDAP server up at: %s', server.url) }) node-ldapjs-2.3.3/examples/snoopldap.d000077500000000000000000000007651424777304200177260ustar00rootroot00000000000000#!/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; } node-ldapjs-2.3.3/lib/000077500000000000000000000000001424777304200144775ustar00rootroot00000000000000node-ldapjs-2.3.3/lib/assert.js000066400000000000000000000025121424777304200163360ustar00rootroot00000000000000// 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 } node-ldapjs-2.3.3/lib/attribute.js000066400000000000000000000072511424777304200170450ustar00rootroot00000000000000// 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' } node-ldapjs-2.3.3/lib/change.js000066400000000000000000000122331424777304200162630ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/client/000077500000000000000000000000001424777304200157555ustar00rootroot00000000000000node-ldapjs-2.3.3/lib/client/client.js000066400000000000000000001124241424777304200175750ustar00rootroot00000000000000'use strict' const requestQueueFactory = require('./request-queue') const messageTrackerFactory = require('./message-tracker') const { MAX_MSGID } = require('./constants') const EventEmitter = require('events').EventEmitter const net = require('net') const tls = require('tls') const util = require('util') const once = require('once') const backoff = require('backoff') const vasync = require('vasync') const assert = require('assert-plus') const VError = require('verror').VError const Attribute = require('../attribute') const Change = require('../change') const Control = require('../controls/index').Control const SearchPager = require('./search_pager') const Protocol = require('../protocol') const dn = require('../dn') const errors = require('../errors') const filters = require('../filters') const messages = require('../messages') const url = require('../url') const CorkedEmitter = require('../corked_emitter') /// --- Globals const AbandonRequest = messages.AbandonRequest const AddRequest = messages.AddRequest const BindRequest = messages.BindRequest const CompareRequest = messages.CompareRequest const DeleteRequest = messages.DeleteRequest const ExtendedRequest = messages.ExtendedRequest const ModifyRequest = messages.ModifyRequest const ModifyDNRequest = messages.ModifyDNRequest const SearchRequest = messages.SearchRequest const UnbindRequest = messages.UnbindRequest const UnbindResponse = messages.UnbindResponse const LDAPResult = messages.LDAPResult const SearchEntry = messages.SearchEntry const SearchReference = messages.SearchReference // var SearchResponse = messages.SearchResponse const Parser = messages.Parser const PresenceFilter = filters.PresenceFilter const ConnectionError = errors.ConnectionError const CMP_EXPECT = [errors.LDAP_COMPARE_TRUE, errors.LDAP_COMPARE_FALSE] // node 0.6 got rid of FDs, so make up a client id for logging let CLIENT_ID = 0 /// --- Internal Helpers function nextClientId () { if (++CLIENT_ID === MAX_MSGID) { return 1 } return CLIENT_ID } function validateControls (controls) { if (Array.isArray(controls)) { controls.forEach(function (c) { if (!(c instanceof Control)) { throw new TypeError('controls must be [Control]') } }) } else if (controls instanceof Control) { controls = [controls] } else { throw new TypeError('controls must be [Control]') } return controls } function ensureDN (input, strict) { if (dn.DN.isDN(input)) { return dn } else if (strict) { return dn.parse(input) } else if (typeof (input) === 'string') { return input } else { throw new Error('invalid DN') } } /// --- API /** * Constructs a new client. * * The options object is required, and must contain either a URL (string) or * a socketPath (string); the socketPath is only if you want to talk to an LDAP * server over a Unix Domain Socket. Additionally, you can pass in a bunyan * option that is the result of `new Logger()`, presumably after you've * configured it. * * @param {Object} options must have either url or socketPath. * @throws {TypeError} on bad input. */ function Client (options) { assert.ok(options) EventEmitter.call(this, options) const self = this this.urls = options.url ? [].concat(options.url).map(url.parse) : [] this._nextServer = 0 // updated in connectSocket() after each connect this.host = undefined this.port = undefined this.secure = undefined this.url = undefined this.tlsOptions = options.tlsOptions this.socketPath = options.socketPath || false this.log = options.log.child({ clazz: 'Client' }, true) this.timeout = parseInt((options.timeout || 0), 10) this.connectTimeout = parseInt((options.connectTimeout || 0), 10) this.idleTimeout = parseInt((options.idleTimeout || 0), 10) if (options.reconnect) { // Fall back to defaults if options.reconnect === true const rOpts = (typeof (options.reconnect) === 'object') ? options.reconnect : {} this.reconnect = { initialDelay: parseInt(rOpts.initialDelay || 100, 10), maxDelay: parseInt(rOpts.maxDelay || 10000, 10), failAfter: parseInt(rOpts.failAfter, 10) || Infinity } } this.strictDN = (options.strictDN !== undefined) ? options.strictDN : true this.queue = requestQueueFactory({ size: parseInt((options.queueSize || 0), 10), timeout: parseInt((options.queueTimeout || 0), 10) }) if (options.queueDisable) { this.queue.freeze() } // Implicitly configure setup action to bind the client if bindDN and // bindCredentials are passed in. This will more closely mimic PooledClient // auto-login behavior. if (options.bindDN !== undefined && options.bindCredentials !== undefined) { this.on('setup', function (clt, cb) { clt.bind(options.bindDN, options.bindCredentials, function (err) { if (err) { if (self._socket) { self._socket.destroy() } self.emit('error', err) } cb(err) }) }) } this._socket = null this.connected = false this.connect() } util.inherits(Client, EventEmitter) module.exports = Client /** * Sends an abandon request to the LDAP server. * * The callback will be invoked as soon as the data is flushed out to the * network, as there is never a response from abandon. * * @param {Number} messageID the messageID to abandon. * @param {Control} controls (optional) either a Control or [Control]. * @param {Function} callback of the form f(err). * @throws {TypeError} on invalid input. */ Client.prototype.abandon = function abandon (messageID, controls, callback) { assert.number(messageID, 'messageID') if (typeof (controls) === 'function') { callback = controls controls = [] } else { controls = validateControls(controls) } assert.func(callback, 'callback') const req = new AbandonRequest({ abandonID: messageID, controls: controls }) return this._send(req, 'abandon', null, callback) } /** * Adds an entry to the LDAP server. * * Entry can be either [Attribute] or a plain JS object where the * values are either a plain value or an array of values. Any value (that's * not an array) will get converted to a string, so keep that in mind. * * @param {String} name the DN of the entry to add. * @param {Object} entry an array of Attributes to be added or a JS object. * @param {Control} controls (optional) either a Control or [Control]. * @param {Function} callback of the form f(err, res). * @throws {TypeError} on invalid input. */ Client.prototype.add = function add (name, entry, controls, callback) { assert.ok(name !== undefined, 'name') assert.object(entry, 'entry') if (typeof (controls) === 'function') { callback = controls controls = [] } else { controls = validateControls(controls) } assert.func(callback, 'callback') if (Array.isArray(entry)) { entry.forEach(function (a) { if (!Attribute.isAttribute(a)) { throw new TypeError('entry must be an Array of Attributes') } }) } else { const save = entry entry = [] Object.keys(save).forEach(function (k) { const attr = new Attribute({ type: k }) if (Array.isArray(save[k])) { save[k].forEach(function (v) { attr.addValue(v.toString()) }) } else if (Buffer.isBuffer(save[k])) { attr.addValue(save[k]) } else { attr.addValue(save[k].toString()) } entry.push(attr) }) } const req = new AddRequest({ entry: ensureDN(name, this.strictDN), attributes: entry, controls: controls }) return this._send(req, [errors.LDAP_SUCCESS], null, callback) } /** * Performs a simple authentication against the server. * * @param {String} name the DN to bind as. * @param {String} credentials the userPassword associated with name. * @param {Control} controls (optional) either a Control or [Control]. * @param {Function} callback of the form f(err, res). * @throws {TypeError} on invalid input. */ Client.prototype.bind = function bind (name, credentials, controls, callback, _bypass) { if (typeof (name) !== 'string' && !(name instanceof dn.DN)) { throw new TypeError('name (string) required') } assert.optionalString(credentials, 'credentials') if (typeof (controls) === 'function') { callback = controls controls = [] } else { controls = validateControls(controls) } assert.func(callback, 'callback') const req = new BindRequest({ name: name || '', authentication: 'Simple', credentials: credentials || '', controls: controls }) // Connection errors will be reported to the bind callback too (useful when the LDAP server is not available) const self = this function callbackWrapper (err, ret) { self.removeListener('connectError', callbackWrapper) callback(err, ret) } this.addListener('connectError', callbackWrapper) return this._send(req, [errors.LDAP_SUCCESS], null, callbackWrapper, _bypass) } /** * Compares an attribute/value pair with an entry on the LDAP server. * * @param {String} name the DN of the entry to compare attributes with. * @param {String} attr name of an attribute to check. * @param {String} value value of an attribute to check. * @param {Control} controls (optional) either a Control or [Control]. * @param {Function} callback of the form f(err, boolean, res). * @throws {TypeError} on invalid input. */ Client.prototype.compare = function compare (name, attr, value, controls, callback) { assert.ok(name !== undefined, 'name') assert.string(attr, 'attr') assert.string(value, 'value') if (typeof (controls) === 'function') { callback = controls controls = [] } else { controls = validateControls(controls) } assert.func(callback, 'callback') const req = new CompareRequest({ entry: ensureDN(name, this.strictDN), attribute: attr, value: value, controls: controls }) return this._send(req, CMP_EXPECT, null, function (err, res) { if (err) { return callback(err) } return callback(null, (res.status === errors.LDAP_COMPARE_TRUE), res) }) } /** * Deletes an entry from the LDAP server. * * @param {String} name the DN of the entry to delete. * @param {Control} controls (optional) either a Control or [Control]. * @param {Function} callback of the form f(err, res). * @throws {TypeError} on invalid input. */ Client.prototype.del = function del (name, controls, callback) { assert.ok(name !== undefined, 'name') if (typeof (controls) === 'function') { callback = controls controls = [] } else { controls = validateControls(controls) } assert.func(callback, 'callback') const req = new DeleteRequest({ entry: ensureDN(name, this.strictDN), controls: controls }) return this._send(req, [errors.LDAP_SUCCESS], null, callback) } /** * Performs an extended operation on the LDAP server. * * Pretty much none of the LDAP extended operations return an OID * (responseName), so I just don't bother giving it back in the callback. * It's on the third param in `res` if you need it. * * @param {String} name the OID of the extended operation to perform. * @param {String} value value to pass in for this operation. * @param {Control} controls (optional) either a Control or [Control]. * @param {Function} callback of the form f(err, value, res). * @throws {TypeError} on invalid input. */ Client.prototype.exop = function exop (name, value, controls, callback) { assert.string(name, 'name') if (typeof (value) === 'function') { callback = value controls = [] value = undefined } if (typeof (controls) === 'function') { callback = controls controls = [] } else { controls = validateControls(controls) } assert.func(callback, 'callback') const req = new ExtendedRequest({ requestName: name, requestValue: value, controls: controls }) return this._send(req, [errors.LDAP_SUCCESS], null, function (err, res) { if (err) { return callback(err) } return callback(null, res.responseValue || '', res) }) } /** * Performs an LDAP modify against the server. * * @param {String} name the DN of the entry to modify. * @param {Change} change update to perform (can be [Change]). * @param {Control} controls (optional) either a Control or [Control]. * @param {Function} callback of the form f(err, res). * @throws {TypeError} on invalid input. */ Client.prototype.modify = function modify (name, change, controls, callback) { assert.ok(name !== undefined, 'name') assert.object(change, 'change') const changes = [] function changeFromObject (obj) { if (!obj.operation && !obj.type) { throw new Error('change.operation required') } if (typeof (obj.modification) !== 'object') { throw new Error('change.modification (object) required') } if (Object.keys(obj.modification).length === 2 && typeof (obj.modification.type) === 'string' && Array.isArray(obj.modification.vals)) { // Use modification directly if it's already normalized: changes.push(new Change({ operation: obj.operation || obj.type, modification: obj.modification })) } else { // Normalize the modification object Object.keys(obj.modification).forEach(function (k) { const mod = {} mod[k] = obj.modification[k] changes.push(new Change({ operation: obj.operation || obj.type, modification: mod })) }) } } if (Change.isChange(change)) { changes.push(change) } else if (Array.isArray(change)) { change.forEach(function (c) { if (Change.isChange(c)) { changes.push(c) } else { changeFromObject(c) } }) } else { changeFromObject(change) } if (typeof (controls) === 'function') { callback = controls controls = [] } else { controls = validateControls(controls) } assert.func(callback, 'callback') const req = new ModifyRequest({ object: ensureDN(name, this.strictDN), changes: changes, controls: controls }) return this._send(req, [errors.LDAP_SUCCESS], null, callback) } /** * Performs an LDAP modifyDN against the server. * * This does not allow you to keep the old DN, as while the LDAP protocol * has a facility for that, it's stupid. Just Search/Add. * * This will automatically deal with "new superior" logic. * * @param {String} name the DN of the entry to modify. * @param {String} newName the new DN to move this entry to. * @param {Control} controls (optional) either a Control or [Control]. * @param {Function} callback of the form f(err, res). * @throws {TypeError} on invalid input. */ Client.prototype.modifyDN = function modifyDN (name, newName, controls, callback) { assert.ok(name !== undefined, 'name') assert.string(newName, 'newName') if (typeof (controls) === 'function') { callback = controls controls = [] } else { controls = validateControls(controls) } assert.func(callback) const DN = ensureDN(name) // TODO: is non-strict handling desired here? const newDN = dn.parse(newName) const req = new ModifyDNRequest({ entry: DN, deleteOldRdn: true, controls: controls }) if (newDN.length !== 1) { req.newRdn = dn.parse(newDN.rdns.shift().toString()) req.newSuperior = newDN } else { req.newRdn = newDN } return this._send(req, [errors.LDAP_SUCCESS], null, callback) } /** * Performs an LDAP search against the server. * * Note that the defaults for options are a 'base' search, if that's what * you want you can just pass in a string for options and it will be treated * as the search filter. Also, you can either pass in programatic Filter * objects or a filter string as the filter option. * * Note that this method is 'special' in that the callback 'res' param will * have two important events on it, namely 'entry' and 'end' that you can hook * to. The former will emit a SearchEntry object for each record that comes * back, and the latter will emit a normal LDAPResult object. * * @param {String} base the DN in the tree to start searching at. * @param {Object} options parameters: * - {String} scope default of 'base'. * - {String} filter default of '(objectclass=*)'. * - {Array} attributes [string] to return. * - {Boolean} attrsOnly whether to return values. * @param {Control} controls (optional) either a Control or [Control]. * @param {Function} callback of the form f(err, res). * @throws {TypeError} on invalid input. */ Client.prototype.search = function search (base, options, controls, callback, _bypass) { assert.ok(base !== undefined, 'search base') if (Array.isArray(options) || (options instanceof Control)) { controls = options options = {} } else if (typeof (options) === 'function') { callback = options controls = [] options = { filter: new PresenceFilter({ attribute: 'objectclass' }) } } else if (typeof (options) === 'string') { options = { filter: filters.parseString(options) } } else if (typeof (options) !== 'object') { throw new TypeError('options (object) required') } if (typeof (options.filter) === 'string') { options.filter = filters.parseString(options.filter) } else if (!options.filter) { options.filter = new PresenceFilter({ attribute: 'objectclass' }) } else if (!filters.isFilter(options.filter)) { throw new TypeError('options.filter (Filter) required') } if (typeof (controls) === 'function') { callback = controls controls = [] } else { controls = validateControls(controls) } assert.func(callback, 'callback') if (options.attributes) { if (!Array.isArray(options.attributes)) { if (typeof (options.attributes) === 'string') { options.attributes = [options.attributes] } else { throw new TypeError('options.attributes must be an Array of Strings') } } } const self = this const baseDN = ensureDN(base, this.strictDN) function sendRequest (ctrls, emitter, cb) { const req = new SearchRequest({ baseObject: baseDN, scope: options.scope || 'base', filter: options.filter, derefAliases: options.derefAliases || Protocol.NEVER_DEREF_ALIASES, sizeLimit: options.sizeLimit || 0, timeLimit: options.timeLimit || 10, typesOnly: options.typesOnly || false, attributes: options.attributes || [], controls: ctrls }) return self._send(req, [errors.LDAP_SUCCESS], emitter, cb, _bypass) } if (options.paged) { // Perform automated search paging const pageOpts = typeof (options.paged) === 'object' ? options.paged : {} let size = 100 // Default page size if (pageOpts.pageSize > 0) { size = pageOpts.pageSize } else if (options.sizeLimit > 1) { // According to the RFC, servers should ignore the paging control if // pageSize >= sizelimit. Some might still send results, but it's safer // to stay under that figure when assigning a default value. size = options.sizeLimit - 1 } const pager = new SearchPager({ callback: callback, controls: controls, pageSize: size, pagePause: pageOpts.pagePause, sendRequest: sendRequest }) pager.begin() } else { sendRequest(controls, new CorkedEmitter(), callback) } } /** * Unbinds this client from the LDAP server. * * Note that unbind does not have a response, so this callback is actually * optional; either way, the client is disconnected. * * @param {Function} callback of the form f(err). * @throws {TypeError} if you pass in callback as not a function. */ Client.prototype.unbind = function unbind (callback) { if (!callback) { callback = function () {} } if (typeof (callback) !== 'function') { throw new TypeError('callback must be a function') } // When the socket closes, it is useful to know whether it was due to a // user-initiated unbind or something else. this.unbound = true if (!this._socket) { return callback() } const req = new UnbindRequest() return this._send(req, 'unbind', null, callback) } /** * Attempt to secure connection with StartTLS. */ Client.prototype.starttls = function starttls (options, controls, callback, _bypass) { assert.optionalObject(options) options = options || {} callback = once(callback) const self = this if (this._starttls) { return callback(new Error('STARTTLS already in progress or active')) } function onSend (sendErr, emitter) { if (sendErr) { callback(sendErr) return } /* * Now that the request has been sent, block all outgoing messages * until an error is received or we successfully complete the setup. */ // TODO: block traffic self._starttls = { started: true } emitter.on('error', function (err) { self._starttls = null callback(err) }) emitter.on('end', function (_res) { const sock = self._socket /* * Unplumb socket data during SSL negotiation. * This will prevent the LDAP parser from stumbling over the TLS * handshake and raising a ruckus. */ sock.removeAllListeners('data') options.socket = sock const secure = tls.connect(options) secure.once('secureConnect', function () { /* * Wire up 'data' and 'error' handlers like the normal socket. * Handling 'end' events isn't necessary since the underlying socket * will handle those. */ secure.removeAllListeners('error') secure.on('data', function onData (data) { self.log.trace('data event: %s', util.inspect(data)) self._tracker.parser.write(data) }) secure.on('error', function (err) { self.log.trace({ err: err }, 'error event: %s', new Error().stack) self.emit('error', err) sock.destroy() }) callback(null) }) secure.once('error', function (err) { // If the SSL negotiation failed, to back to plain mode. self._starttls = null secure.removeAllListeners() callback(err) }) self._starttls.success = true self._socket = secure }) } const req = new ExtendedRequest({ requestName: '1.3.6.1.4.1.1466.20037', requestValue: null, controls: controls }) return this._send(req, [errors.LDAP_SUCCESS], new EventEmitter(), onSend, _bypass) } /** * Disconnect from the LDAP server and do not allow reconnection. * * If the client is instantiated with proper reconnection options, it's * possible to initiate new requests after a call to unbind since the client * will attempt to reconnect in order to fulfill the request. * * Calling destroy will prevent any further reconnection from occurring. * * @param {Object} err (Optional) error that was cause of client destruction */ Client.prototype.destroy = function destroy (err) { this.destroyed = true this.queue.freeze() // Purge any queued requests which are now meaningless this.queue.flush(function (msg, expect, emitter, cb) { if (typeof (cb) === 'function') { cb(new Error('client destroyed')) } }) if (this.connected) { this.unbind() } if (this._socket) { this._socket.destroy() } this.emit('destroy', err) } /** * Initiate LDAP connection. */ Client.prototype.connect = function connect () { if (this.connecting || this.connected) { return } const self = this const log = this.log let socket let tracker // Establish basic socket connection function connectSocket (cb) { const server = self.urls[self._nextServer] self._nextServer = (self._nextServer + 1) % self.urls.length cb = once(cb) function onResult (err, res) { if (err) { if (self.connectTimer) { clearTimeout(self.connectTimer) self.connectTimer = null } self.emit('connectError', err) } cb(err, res) } function onConnect () { if (self.connectTimer) { clearTimeout(self.connectTimer) self.connectTimer = null } socket.removeAllListeners('error') .removeAllListeners('connect') .removeAllListeners('secureConnect') tracker.id = nextClientId() + '__' + tracker.id self.log = self.log.child({ ldap_id: tracker.id }, true) // Move on to client setup setupClient(cb) } const port = (server && server.port) || self.socketPath const host = server && server.hostname if (server && server.secure) { socket = tls.connect(port, host, self.tlsOptions) socket.once('secureConnect', onConnect) } else { socket = net.connect(port, host) socket.once('connect', onConnect) } socket.once('error', onResult) initSocket(server) // Setup connection timeout handling, if desired if (self.connectTimeout) { self.connectTimer = setTimeout(function onConnectTimeout () { if (!socket || !socket.readable || !socket.writeable) { socket.destroy() self._socket = null onResult(new ConnectionError('connection timeout')) } }, self.connectTimeout) } } // Initialize socket events and LDAP parser. function initSocket (server) { tracker = messageTrackerFactory({ id: server ? server.href : self.socketPath, parser: new Parser({ log: log }) }) // This won't be set on TLS. So. Very. Annoying. if (typeof (socket.setKeepAlive) !== 'function') { socket.setKeepAlive = function setKeepAlive (enable, delay) { return socket.socket ? socket.socket.setKeepAlive(enable, delay) : false } } socket.on('data', function onData (data) { log.trace('data event: %s', util.inspect(data)) tracker.parser.write(data) }) // The "router" tracker.parser.on('message', function onMessage (message) { message.connection = self._socket const callback = tracker.fetch(message.messageID) if (!callback) { log.error({ message: message.json }, 'unsolicited message') return false } return callback(message) }) tracker.parser.on('error', function onParseError (err) { self.emit('error', new VError(err, 'Parser error for %s', tracker.id)) self.connected = false socket.end() }) } // After connect, register socket event handlers and run any setup actions function setupClient (cb) { cb = once(cb) // Indicate failure if anything goes awry during setup function bail (err) { socket.destroy() cb(err || new Error('client error during setup')) } // Work around lack of close event on tls.socket in node < 0.11 ((socket.socket) ? socket.socket : socket).once('close', bail) socket.once('error', bail) socket.once('end', bail) socket.once('timeout', bail) socket.once('cleanupSetupListeners', function onCleanup () { socket.removeListener('error', bail) .removeListener('close', bail) .removeListener('end', bail) .removeListener('timeout', bail) }) self._socket = socket self._tracker = tracker // Run any requested setup (such as automatically performing a bind) on // socket before signalling successful connection. // This setup needs to bypass the request queue since all other activity is // blocked until the connection is considered fully established post-setup. // Only allow bind/search/starttls for now. const basicClient = { bind: function bindBypass (name, credentials, controls, callback) { return self.bind(name, credentials, controls, callback, true) }, search: function searchBypass (base, options, controls, callback) { return self.search(base, options, controls, callback, true) }, starttls: function starttlsBypass (options, controls, callback) { return self.starttls(options, controls, callback, true) }, unbind: self.unbind.bind(self) } vasync.forEachPipeline({ func: function (f, callback) { f(basicClient, callback) }, inputs: self.listeners('setup') }, function (err, _res) { if (err) { self.emit('setupError', err) } cb(err) }) } // Wire up "official" event handlers after successful connect/setup function postSetup () { // cleanup the listeners we attached in setup phrase. socket.emit('cleanupSetupListeners'); // Work around lack of close event on tls.socket in node < 0.11 ((socket.socket) ? socket.socket : socket).once('close', self._onClose.bind(self)) socket.on('end', function onEnd () { log.trace('end event') self.emit('end') socket.end() }) socket.on('error', function onSocketError (err) { log.trace({ err: err }, 'error event: %s', new Error().stack) self.emit('error', err) socket.destroy() }) socket.on('timeout', function onTimeout () { log.trace('timeout event') self.emit('socketTimeout') socket.end() }) const server = self.urls[self._nextServer] if (server) { self.host = server.hostname self.port = server.port self.secure = server.secure } } let retry let failAfter if (this.reconnect) { retry = backoff.exponential({ initialDelay: this.reconnect.initialDelay, maxDelay: this.reconnect.maxDelay }) failAfter = this.reconnect.failAfter if (this.urls.length > 1 && failAfter) { failAfter *= this.urls.length } } else { retry = backoff.exponential({ initialDelay: 1, maxDelay: 2 }) failAfter = this.urls.length || 1 } retry.failAfter(failAfter) retry.on('ready', function (num, _delay) { if (self.destroyed) { // Cease connection attempts if destroyed return } connectSocket(function (err) { if (!err) { postSetup() self.connecting = false self.connected = true self.emit('connect', socket) self.log.debug('connected after %d attempt(s)', num + 1) // Flush any queued requests self._flushQueue() self._connectRetry = null } else { retry.backoff(err) } }) }) retry.on('fail', function (err) { if (self.destroyed) { // Silence any connect/setup errors if destroyed return } self.log.debug('failed to connect after %d attempts', failAfter) // Communicate the last-encountered error if (err instanceof ConnectionError) { self.emitError('connectTimeout', err) } else if (err.code === 'ECONNREFUSED') { self.emitError('connectRefused', err) } else { self.emit('error', err) } }) this._connectRetry = retry this.connecting = true retry.backoff() } /// --- Private API /** * Flush queued requests out to the socket. */ Client.prototype._flushQueue = function _flushQueue () { // Pull items we're about to process out of the queue. this.queue.flush(this._send.bind(this)) } /** * Clean up socket/parser resources after socket close. */ Client.prototype._onClose = function _onClose (closeError) { const socket = this._socket const tracker = this._tracker socket.removeAllListeners('connect') .removeAllListeners('data') .removeAllListeners('drain') .removeAllListeners('end') .removeAllListeners('error') .removeAllListeners('timeout') this._socket = null this.connected = false; ((socket.socket) ? socket.socket : socket).removeAllListeners('close') this.log.trace('close event had_err=%s', closeError ? 'yes' : 'no') this.emit('close', closeError) // On close we have to walk the outstanding messages and go invoke their // callback with an error. tracker.purge(function (msgid, cb) { if (socket.unbindMessageID !== msgid) { return cb(new ConnectionError(tracker.id + ' closed')) } else { // Unbinds will be communicated as a success since we're closed const unbind = new UnbindResponse({ messageID: msgid }) unbind.status = 'unbind' return cb(unbind) } }) // Trash any parser or starttls state this._tracker = null delete this._starttls // Automatically fire reconnect logic if the socket was closed for any reason // other than a user-initiated unbind. if (this.reconnect && !this.unbound) { this.connect() } this.unbound = false return false } /** * Maintain idle timer for client. * * Will start timer to fire 'idle' event if conditions are satisfied. If * conditions are not met and a timer is running, it will be cleared. * * @param {Boolean} override explicitly disable timer. */ Client.prototype._updateIdle = function _updateIdle (override) { if (this.idleTimeout === 0) { return } // Client must be connected but not waiting on any request data const self = this function isIdle (disable) { return ((disable !== true) && (self._socket && self.connected) && (self._tracker.pending === 0)) } if (isIdle(override)) { if (!this._idleTimer) { this._idleTimer = setTimeout(function () { // Double-check idleness in case socket was torn down if (isIdle()) { self.emit('idle') } }, this.idleTimeout) } } else { if (this._idleTimer) { clearTimeout(this._idleTimer) this._idleTimer = null } } } /** * Attempt to send an LDAP request. */ Client.prototype._send = function _send (message, expect, emitter, callback, _bypass) { assert.ok(message) assert.ok(expect) assert.optionalObject(emitter) assert.ok(callback) // Allow connect setup traffic to bypass checks if (_bypass && this._socket && this._socket.writable) { return this._sendSocket(message, expect, emitter, callback) } if (!this._socket || !this.connected) { if (!this.queue.enqueue(message, expect, emitter, callback)) { callback(new ConnectionError('connection unavailable')) } // Initiate reconnect if needed if (this.reconnect) { this.connect() } return false } else { this._flushQueue() return this._sendSocket(message, expect, emitter, callback) } } Client.prototype._sendSocket = function _sendSocket (message, expect, emitter, callback) { const conn = this._socket const tracker = this._tracker const log = this.log const self = this let timer = false let sentEmitter = false function sendResult (event, obj) { if (event === 'error') { self.emit('resultError', obj) } if (emitter) { if (event === 'error') { // Error will go unhandled if emitter hasn't been sent via callback. // Execute callback with the error instead. if (!sentEmitter) { return callback(obj) } } return emitter.emit(event, obj) } if (event === 'error') { return callback(obj) } return callback(null, obj) } function messageCallback (msg) { if (timer) { clearTimeout(timer) } log.trace({ msg: msg ? msg.json : null }, 'response received') if (expect === 'abandon') { return sendResult('end', null) } if (msg instanceof SearchEntry || msg instanceof SearchReference) { let event = msg.constructor.name event = event[0].toLowerCase() + event.slice(1) return sendResult(event, msg) } else { tracker.remove(message.messageID) // Potentially mark client as idle self._updateIdle() if (msg instanceof LDAPResult) { if (expect.indexOf(msg.status) === -1) { return sendResult('error', errors.getError(msg)) } return sendResult('end', msg) } else if (msg instanceof Error) { return sendResult('error', msg) } else { return sendResult('error', new errors.ProtocolError(msg.type)) } } } function onRequestTimeout () { self.emit('timeout', message) const cb = tracker.fetch(message.messageID) if (cb) { // FIXME: the timed-out request should be abandoned cb(new errors.TimeoutError('request timeout (client interrupt)')) } } function writeCallback () { if (expect === 'abandon') { // Mark the messageID specified as abandoned tracker.abandon(message.abandonID) // No need to track the abandon request itself tracker.remove(message.id) return callback(null) } else if (expect === 'unbind') { conn.unbindMessageID = message.id // Mark client as disconnected once unbind clears the socket self.connected = false // Some servers will RST the connection after receiving an unbind. // Socket errors are blackholed since the connection is being closed. conn.removeAllListeners('error') conn.on('error', function () {}) conn.end() } else if (emitter) { sentEmitter = true callback(null, emitter) emitter.emit('searchRequest', message) return } return false } // Start actually doing something... tracker.track(message, messageCallback) // Mark client as active this._updateIdle(true) if (self.timeout) { log.trace('Setting timeout to %d', self.timeout) timer = setTimeout(onRequestTimeout, self.timeout) } log.trace('sending request %j', message.json) try { return conn.write(message.toBer(), writeCallback) } catch (e) { if (timer) { clearTimeout(timer) } log.trace({ err: e }, 'Error writing message to socket') return callback(e) } } Client.prototype.emitError = function emitError (event, err) { if (event !== 'error' && err && this.listenerCount(event) === 0) { if (typeof err === 'string') { err = event + ': ' + err } else if (err.message) { err.message = event + ': ' + err.message } this.emit('error', err) } this.emit(event, err) } node-ldapjs-2.3.3/lib/client/constants.js000066400000000000000000000002701424777304200203260ustar00rootroot00000000000000'use strict' module.exports = { // https://tools.ietf.org/html/rfc4511#section-4.1.1 // Message identifiers are an integer between (0, maxint). MAX_MSGID: Math.pow(2, 31) - 1 } node-ldapjs-2.3.3/lib/client/index.js000066400000000000000000000020071424777304200174210ustar00rootroot00000000000000'use strict' const logger = require('../logger') const Client = require('./client') module.exports = { Client: Client, createClient: function createClient (options) { if (isObject(options) === false) throw TypeError('options (object) required') if (options.url && typeof options.url !== 'string' && !Array.isArray(options.url)) throw TypeError('options.url (string|array) required') if (options.socketPath && typeof options.socketPath !== 'string') throw TypeError('options.socketPath must be a string') if ((options.url && options.socketPath) || !(options.url || options.socketPath)) throw TypeError('options.url ^ options.socketPath (String) required') if (!options.log) options.log = logger if (isObject(options.log) !== true) throw TypeError('options.log must be an object') if (!options.log.child) options.log.child = function () { return options.log } return new Client(options) } } function isObject (input) { return Object.prototype.toString.apply(input) === '[object Object]' } node-ldapjs-2.3.3/lib/client/message-tracker/000077500000000000000000000000001424777304200210325ustar00rootroot00000000000000node-ldapjs-2.3.3/lib/client/message-tracker/ge-window.js000066400000000000000000000012501424777304200232660ustar00rootroot00000000000000'use strict' const { MAX_MSGID } = require('../constants') /** * Compare a reference id with another id to determine "greater than or equal" * between the two values according to a sliding window. * * @param {integer} ref * @param {integer} comp * * @returns {boolean} `true` if the `comp` value is >= to the `ref` value * within the computed window, otherwise `false`. */ module.exports = function geWindow (ref, comp) { let max = ref + Math.floor(MAX_MSGID / 2) const min = ref if (max >= MAX_MSGID) { // Handle roll-over max = max - MAX_MSGID - 1 return ((comp <= max) || (comp >= min)) } else { return ((comp <= max) && (comp >= min)) } } node-ldapjs-2.3.3/lib/client/message-tracker/id-generator.js000066400000000000000000000014211424777304200237460ustar00rootroot00000000000000'use strict' const { MAX_MSGID } = require('../constants') /** * Returns a function that generates message identifiers. According to RFC 4511 * the identifers should be `(0, MAX_MSGID)`. The returned function handles * this and wraps around when the maximum has been reached. * * @param {integer} [start=0] Starting number in the identifier sequence. * * @returns {function} This function accepts no parameters and returns an * increasing sequence identifier each invocation until it reaches the maximum * identifier. At this point the sequence starts over. */ module.exports = function idGeneratorFactory (start = 0) { let currentID = start return function nextID () { const id = currentID + 1 currentID = (id >= MAX_MSGID) ? 1 : id return currentID } } node-ldapjs-2.3.3/lib/client/message-tracker/index.js000066400000000000000000000103621424777304200225010ustar00rootroot00000000000000'use strict' const idGeneratorFactory = require('./id-generator') const purgeAbandoned = require('./purge-abandoned') /** * Returns a message tracker object that keeps track of which message * identifiers correspond to which message handlers. Also handles keeping track * of abandoned messages. * * @param {object} options * @param {string} options.id An identifier for the tracker. * @param {object} options.parser An object that will be used to parse messages. * * @returns {MessageTracker} */ module.exports = function messageTrackerFactory (options) { if (Object.prototype.toString.call(options) !== '[object Object]') { throw Error('options object is required') } if (!options.id || typeof options.id !== 'string') { throw Error('options.id string is required') } if (!options.parser || Object.prototype.toString.call(options.parser) !== '[object Object]') { throw Error('options.parser object is required') } let currentID = 0 const nextID = idGeneratorFactory() const messages = new Map() const abandoned = new Map() /** * @typedef {object} MessageTracker * @property {string} id The identifier of the tracker as supplied via the options. * @property {object} parser The parser object given by the the options. */ const tracker = { id: options.id, parser: options.parser } /** * Count of messages awaiting response. * * @alias pending * @memberof! MessageTracker# */ Object.defineProperty(tracker, 'pending', { get () { return messages.size } }) /** * Move a specific message to the abanded track. * * @param {integer} msgID The identifier for the message to move. * * @memberof MessageTracker * @method abandon */ tracker.abandon = function abandonMessage (msgID) { if (messages.has(msgID) === false) return false abandoned.set(msgID, { age: currentID, cb: messages.get(msgID) }) return messages.delete(msgID) } /** * Retrieves the message handler for a message. Removes abandoned messages * that have been given time to be resolved. * * @param {integer} msgID The identifier for the message to get the handler for. * * @memberof MessageTracker * @method fetch */ tracker.fetch = function fetchMessage (msgID) { const messageCB = messages.get(msgID) if (messageCB) { purgeAbandoned(msgID, abandoned) return messageCB } // We sent an abandon request but the server either wasn't able to process // it or has not received it yet. Therefore, we received a response for the // abandoned message. So we must return the abandoned message's callback // to be processed normally. const abandonedMsg = abandoned.get(msgID) if (abandonedMsg) { return abandonedMsg.cb } return null } /** * Removes all message tracks, cleans up the abandoned track, and invokes * a callback for each message purged. * * @param {function} cb A function with the signature `(msgID, handler)`. * * @memberof MessageTracker * @method purge */ tracker.purge = function purgeMessages (cb) { messages.forEach((val, key) => { purgeAbandoned(key, abandoned) tracker.remove(key) cb(key, val) }) } /** * Removes a message from all tracking. * * @param {integer} msgID The identifier for the message to remove from tracking. * * @memberof MessageTracker * @method remove */ tracker.remove = function removeMessage (msgID) { if (messages.delete(msgID) === false) { abandoned.delete(msgID) } } /** * Add a message handler to be tracked. * * @param {object} message The message object to be tracked. This object will * have a new property added to it: `messageID`. * @param {function} callback The handler for the message. * * @memberof MessageTracker * @method track */ tracker.track = function trackMessage (message, callback) { currentID = nextID() // This side effect is not ideal but the client doesn't attach the tracker // to itself until after the `.connect` method has fired. If this can be // refactored later, then we can possibly get rid of this side effect. message.messageID = currentID messages.set(currentID, callback) } return tracker } node-ldapjs-2.3.3/lib/client/message-tracker/purge-abandoned.js000066400000000000000000000026011424777304200244220ustar00rootroot00000000000000'use strict' const { AbandonedError } = require('../../errors') const geWindow = require('./ge-window') /** * Given a `msgID` and a set of `abandoned` messages, remove any abandoned * messages that existed _prior_ to the specified `msgID`. For example, let's * assume the server has sent 3 messages: * * 1. A search message. * 2. An abandon message for the search message. * 3. A new search message. * * When the response for message #1 comes in, if it does, it will be processed * normally due to the specification. Message #2 will not receive a response, or * if the server does send one since the spec sort of allows it, we won't do * anything with it because we just discard that listener. Now the response * for message #3 comes in. At this point, we will issue a purge of responses * by passing in `msgID = 3`. This result is that we will remove the tracking * for message #1. * * @param {integer} msgID An upper bound for the messages to be purged. * @param {Map} abandoned A set of abandoned messages. Each message is an object * `{ age: , cb: }` where `age` was the current message id when the * abandon message was sent. */ module.exports = function purgeAbandoned (msgID, abandoned) { abandoned.forEach((val, key) => { if (geWindow(val.age, msgID) === false) return val.cb(new AbandonedError('client request abandoned')) abandoned.delete(key) }) } node-ldapjs-2.3.3/lib/client/request-queue/000077500000000000000000000000001424777304200205675ustar00rootroot00000000000000node-ldapjs-2.3.3/lib/client/request-queue/enqueue.js000066400000000000000000000022111424777304200225700ustar00rootroot00000000000000'use strict' /** * Adds requests to the queue. If a timeout has been added to the queue then * this will freeze the queue with the newly added item, flush it, and then * unfreeze it when the queue has been cleared. * * @param {object} message An LDAP message object. * @param {object} expect An expectation object. * @param {object} emitter An event emitter or `null`. * @param {function} cb A callback to invoke when the request is finished. * * @returns {boolean} `true` if the requested was queued. `false` if the queue * is not accepting any requests. */ module.exports = function enqueue (message, expect, emitter, cb) { if (this._queue.length >= this.size || this._frozen) { return false } this._queue.add({ message, expect, emitter, cb }) if (this.timeout === 0) return true if (this._timer === null) return true // A queue can have a specified time allotted for it to be cleared. If that // time has been reached, reject new entries until the queue has been cleared. this._timer = setTimeout(queueTimeout.bind(this), this.timeout) return true function queueTimeout () { this.freeze() this.purge() } } node-ldapjs-2.3.3/lib/client/request-queue/flush.js000066400000000000000000000013561424777304200222530ustar00rootroot00000000000000'use strict' /** * Invokes all requests in the queue by passing them to the supplied callback * function and then clears all items from the queue. * * @param {function} cb A function used to handle the requests. */ module.exports = function flush (cb) { if (this._timer) { clearTimeout(this._timer) this._timer = null } // We must get a local copy of the queue and clear it before iterating it. // The client will invoke this flush function _many_ times. If we try to // iterate it without a local copy and clearing first then we will overflow // the stack. const requests = Array.from(this._queue.values()) this._queue.clear() for (const req of requests) { cb(req.message, req.expect, req.emitter, req.cb) } } node-ldapjs-2.3.3/lib/client/request-queue/index.js000066400000000000000000000017501424777304200222370ustar00rootroot00000000000000'use strict' const enqueue = require('./enqueue') const flush = require('./flush') const purge = require('./purge') /** * Builds a request queue object and returns it. * * @param {object} [options] * @param {integer} [options.size] Maximum size of the request queue. Must be * a number greater than `0` if supplied. Default: `Infinity`. * @param {integer} [options.timeout] Time in milliseconds a queue has to * complete the requests it contains. * * @returns {object} A queue instance. */ module.exports = function requestQueueFactory (options) { const opts = Object.assign({}, options) const q = { size: (opts.size > 0) ? opts.size : Infinity, timeout: (opts.timeout > 0) ? opts.timeout : 0, _queue: new Set(), _timer: null, _frozen: false } q.enqueue = enqueue.bind(q) q.flush = flush.bind(q) q.purge = purge.bind(q) q.freeze = function freeze () { this._frozen = true } q.thaw = function thaw () { this._frozen = false } return q } node-ldapjs-2.3.3/lib/client/request-queue/purge.js000066400000000000000000000004411424777304200222460ustar00rootroot00000000000000'use strict' const { TimeoutError } = require('../../errors') /** * Flushes the queue by rejecting all pending requests with a timeout error. */ module.exports = function purge () { this.flush(function flushCB (a, b, c, cb) { cb(new TimeoutError('request queue timeout')) }) } node-ldapjs-2.3.3/lib/client/search_pager.js000066400000000000000000000121421424777304200207360ustar00rootroot00000000000000'use strict' const EventEmitter = require('events').EventEmitter const util = require('util') const assert = require('assert-plus') // var dn = require('../dn') // var messages = require('../messages/index') // var Protocol = require('../protocol') const PagedControl = require('../controls/paged_results_control.js') const CorkedEmitter = require('../corked_emitter.js') /// --- API /** * Handler object for paged search operations. * * Provided to consumers in place of the normal search EventEmitter it adds the * following new events: * 1. page - Emitted whenever the end of a result page is encountered. * If this is the last page, 'end' will also be emitted. * The event passes two arguments: * 1. The result object (similar to 'end') * 2. A callback function optionally used to continue the search * operation if the pagePause option was specified during * initialization. * 2. pageError - Emitted if the server does not support paged search results * If there are no listeners for this event, the 'error' event * will be emitted (and 'end' will not be). By listening to * 'pageError', a successful search that lacks paging will be * able to emit 'end'. */ function SearchPager (opts) { assert.object(opts) assert.func(opts.callback) assert.number(opts.pageSize) assert.func(opts.sendRequest) CorkedEmitter.call(this, {}) this.callback = opts.callback this.controls = opts.controls this.pageSize = opts.pageSize this.pagePause = opts.pagePause this.sendRequest = opts.sendRequest this.controls.forEach(function (control) { if (control.type === PagedControl.OID) { // The point of using SearchPager is not having to do this. // Toss an error if the pagedResultsControl is present throw new Error('redundant pagedResultControl') } }) this.finished = false this.started = false const emitter = new EventEmitter() emitter.on('searchRequest', this.emit.bind(this, 'searchRequest')) emitter.on('searchEntry', this.emit.bind(this, 'searchEntry')) emitter.on('end', this._onEnd.bind(this)) emitter.on('error', this._onError.bind(this)) this.childEmitter = emitter } util.inherits(SearchPager, CorkedEmitter) module.exports = SearchPager /** * Start the paged search. */ SearchPager.prototype.begin = function begin () { // Starting first page this._nextPage(null) } SearchPager.prototype._onEnd = function _onEnd (res) { const self = this let cookie = null res.controls.forEach(function (control) { if (control.type === PagedControl.OID) { cookie = control.value.cookie } }) // Pass a noop callback by default for page events const nullCb = function () { } if (cookie === null) { // paged search not supported this.finished = true this.emit('page', res, nullCb) const err = new Error('missing paged control') err.name = 'PagedError' if (this.listeners('pageError').length > 0) { this.emit('pageError', err) // If the consumer as subscribed to pageError, SearchPager is absolved // from deliverying the fault via the 'error' event. Emitting an 'end' // event after 'error' breaks the contract that the standard client // provides, so it's only a possibility if 'pageError' is used instead. this.emit('end', res) } else { this.emit('error', err) // No end event possible per explaination above. } return } if (cookie.length === 0) { // end of paged results this.finished = true this.emit('page', nullCb) this.emit('end', res) } else { if (this.pagePause) { // Wait to fetch next page until callback is invoked // Halt page fetching if called with error this.emit('page', res, function (err) { if (!err) { self._nextPage(cookie) } else { // the paged search has been canceled so emit an end self.emit('end', res) } }) } else { this.emit('page', res, nullCb) this._nextPage(cookie) } } } SearchPager.prototype._onError = function _onError (err) { this.finished = true this.emit('error', err) } /** * Initiate a search for the next page using the returned cookie value. */ SearchPager.prototype._nextPage = function _nextPage (cookie) { const controls = this.controls.slice(0) controls.push(new PagedControl({ value: { size: this.pageSize, cookie: cookie } })) this.sendRequest(controls, this.childEmitter, this._sendCallback.bind(this)) } /** * Callback provided to the client API for successful transmission. */ SearchPager.prototype._sendCallback = function _sendCallback (err) { if (err) { this.finished = true if (!this.started) { // EmitSend error during the first page, bail via callback this.callback(err, null) } else { this.emit('error', err) } } else { // search successfully send if (!this.started) { this.started = true // send self as emitter as the client would this.callback(null, this) } } } node-ldapjs-2.3.3/lib/controls/000077500000000000000000000000001424777304200163425ustar00rootroot00000000000000node-ldapjs-2.3.3/lib/controls/control.js000066400000000000000000000024671424777304200203710ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/controls/entry_change_notification_control.js000066400000000000000000000040251424777304200256550ustar00rootroot00000000000000const 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 node-ldapjs-2.3.3/lib/controls/index.js000066400000000000000000000052271424777304200200150ustar00rootroot00000000000000// Copyright 2011 Mark Cavage, Inc. All rights reserved. const assert = require('assert') const Ber = require('asn1').Ber const Control = require('./control') const EntryChangeNotificationControl = require('./entry_change_notification_control') const PersistentSearchControl = require('./persistent_search_control') const PagedResultsControl = require('./paged_results_control') const ServerSideSortingRequestControl = require('./server_side_sorting_request_control.js') const ServerSideSortingResponseControl = require('./server_side_sorting_response_control.js') const VirtualListViewRequestControl = require('./virtual_list_view_request_control.js') const VirtualListViewResponseControl = require('./virtual_list_view_response_control.js') /// --- API module.exports = { getControl: function getControl (ber) { assert.ok(ber) if (ber.readSequence() === null) { return null } let type const opts = { criticality: false, value: null } if (ber.length) { const end = ber.offset + ber.length type = ber.readString() if (ber.offset < end) { if (ber.peek() === Ber.Boolean) { opts.criticality = ber.readBoolean() } } if (ber.offset < end) { opts.value = ber.readString(Ber.OctetString, true) } } let control switch (type) { case PersistentSearchControl.OID: control = new PersistentSearchControl(opts) break case EntryChangeNotificationControl.OID: control = new EntryChangeNotificationControl(opts) break case PagedResultsControl.OID: control = new PagedResultsControl(opts) break case ServerSideSortingRequestControl.OID: control = new ServerSideSortingRequestControl(opts) break case ServerSideSortingResponseControl.OID: control = new ServerSideSortingResponseControl(opts) break case VirtualListViewRequestControl.OID: control = new VirtualListViewRequestControl(opts) break case VirtualListViewResponseControl.OID: control = new VirtualListViewResponseControl(opts) break default: opts.type = type control = new Control(opts) break } return control }, Control: Control, EntryChangeNotificationControl: EntryChangeNotificationControl, PagedResultsControl: PagedResultsControl, PersistentSearchControl: PersistentSearchControl, ServerSideSortingRequestControl: ServerSideSortingRequestControl, ServerSideSortingResponseControl: ServerSideSortingResponseControl, VirtualListViewRequestControl: VirtualListViewRequestControl, VirtualListViewResponseControl: VirtualListViewResponseControl } node-ldapjs-2.3.3/lib/controls/paged_results_control.js000066400000000000000000000037571424777304200233150ustar00rootroot00000000000000const 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 node-ldapjs-2.3.3/lib/controls/persistent_search_control.js000066400000000000000000000035451424777304200241740ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/controls/server_side_sorting_request_control.js000066400000000000000000000057121424777304200262740ustar00rootroot00000000000000const 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 node-ldapjs-2.3.3/lib/controls/server_side_sorting_response_control.js000066400000000000000000000052701424777304200264410ustar00rootroot00000000000000const 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 node-ldapjs-2.3.3/lib/controls/virtual_list_view_request_control.js000066400000000000000000000052001424777304200257600ustar00rootroot00000000000000const 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 node-ldapjs-2.3.3/lib/controls/virtual_list_view_response_control.js000066400000000000000000000060071424777304200261340ustar00rootroot00000000000000const 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 node-ldapjs-2.3.3/lib/corked_emitter.js000066400000000000000000000033741424777304200200440ustar00rootroot00000000000000'use strict' const EventEmitter = require('events').EventEmitter /** * A CorkedEmitter is a variant of an EventEmitter where events emitted * wait for the appearance of the first listener of any kind. That is, * a CorkedEmitter will store all .emit()s it receives, to be replayed * later when an .on() is applied. * It is meant for situations where the consumers of the emitter are * unable to register listeners right away, and cannot afford to miss * any events emitted from the start. * Note that, whenever the first emitter (for any event) appears, * the emitter becomes uncorked and works as usual for ALL events, and * will not cache anything anymore. This is necessary to avoid * re-ordering emits - either everything is being buffered, or nothing. */ function CorkedEmitter () { const self = this EventEmitter.call(self) /** * An array of arguments objects (array-likes) to emit on open. */ self._outstandingEmits = [] /** * Whether the normal flow of emits is restored yet. */ self._opened = false // When the first listener appears, we enqueue an opening. // It is not done immediately, so that other listeners can be // registered in the same critical section. self.once('newListener', function () { setImmediate(function releaseStoredEvents () { self._opened = true self._outstandingEmits.forEach(function (args) { self.emit.apply(self, args) }) }) }) } CorkedEmitter.prototype = Object.create(EventEmitter.prototype) CorkedEmitter.prototype.emit = function emit (eventName) { if (this._opened || eventName === 'newListener') { EventEmitter.prototype.emit.apply(this, arguments) } else { this._outstandingEmits.push(arguments) } } module.exports = CorkedEmitter node-ldapjs-2.3.3/lib/dn.js000066400000000000000000000253461424777304200154500ustar00rootroot00000000000000// 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 } node-ldapjs-2.3.3/lib/dtrace.js000066400000000000000000000063451424777304200163070ustar00rootroot00000000000000// 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 }()) node-ldapjs-2.3.3/lib/errors/000077500000000000000000000000001424777304200160135ustar00rootroot00000000000000node-ldapjs-2.3.3/lib/errors/codes.js000066400000000000000000000025401424777304200174470ustar00rootroot00000000000000'use strict' module.exports = { LDAP_SUCCESS: 0, LDAP_OPERATIONS_ERROR: 1, LDAP_PROTOCOL_ERROR: 2, LDAP_TIME_LIMIT_EXCEEDED: 3, LDAP_SIZE_LIMIT_EXCEEDED: 4, LDAP_COMPARE_FALSE: 5, LDAP_COMPARE_TRUE: 6, LDAP_AUTH_METHOD_NOT_SUPPORTED: 7, LDAP_STRONG_AUTH_REQUIRED: 8, LDAP_REFERRAL: 10, LDAP_ADMIN_LIMIT_EXCEEDED: 11, LDAP_UNAVAILABLE_CRITICAL_EXTENSION: 12, LDAP_CONFIDENTIALITY_REQUIRED: 13, LDAP_SASL_BIND_IN_PROGRESS: 14, LDAP_NO_SUCH_ATTRIBUTE: 16, LDAP_UNDEFINED_ATTRIBUTE_TYPE: 17, LDAP_INAPPROPRIATE_MATCHING: 18, LDAP_CONSTRAINT_VIOLATION: 19, LDAP_ATTRIBUTE_OR_VALUE_EXISTS: 20, LDAP_INVALID_ATTRIBUTE_SYNTAX: 21, LDAP_NO_SUCH_OBJECT: 32, LDAP_ALIAS_PROBLEM: 33, LDAP_INVALID_DN_SYNTAX: 34, LDAP_ALIAS_DEREF_PROBLEM: 36, LDAP_INAPPROPRIATE_AUTHENTICATION: 48, LDAP_INVALID_CREDENTIALS: 49, LDAP_INSUFFICIENT_ACCESS_RIGHTS: 50, LDAP_BUSY: 51, LDAP_UNAVAILABLE: 52, LDAP_UNWILLING_TO_PERFORM: 53, LDAP_LOOP_DETECT: 54, LDAP_SORT_CONTROL_MISSING: 60, LDAP_INDEX_RANGE_ERROR: 61, LDAP_NAMING_VIOLATION: 64, LDAP_OBJECTCLASS_VIOLATION: 65, LDAP_NOT_ALLOWED_ON_NON_LEAF: 66, LDAP_NOT_ALLOWED_ON_RDN: 67, LDAP_ENTRY_ALREADY_EXISTS: 68, LDAP_OBJECTCLASS_MODS_PROHIBITED: 69, LDAP_AFFECTS_MULTIPLE_DSAS: 71, LDAP_CONTROL_ERROR: 76, LDAP_OTHER: 80, LDAP_PROXIED_AUTHORIZATION_DENIED: 123 } node-ldapjs-2.3.3/lib/errors/index.js000066400000000000000000000072731424777304200174710ustar00rootroot00000000000000'use strict' const util = require('util') const assert = require('assert-plus') const LDAPResult = require('../messages').LDAPResult /// --- Globals const CODES = require('./codes') const ERRORS = [] /// --- Error Base class function LDAPError (message, dn, caller) { if (Error.captureStackTrace) { Error.captureStackTrace(this, caller || LDAPError) } this.lde_message = message this.lde_dn = dn } util.inherits(LDAPError, Error) Object.defineProperties(LDAPError.prototype, { name: { get: function getName () { return 'LDAPError' }, configurable: false }, code: { get: function getCode () { return CODES.LDAP_OTHER }, configurable: false }, message: { get: function getMessage () { return this.lde_message || this.name }, set: function setMessage (message) { this.lde_message = message }, configurable: false }, dn: { get: function getDN () { return (this.lde_dn ? this.lde_dn.toString() : '') }, configurable: false } }) /// --- Exported API module.exports = {} module.exports.LDAPError = LDAPError // Some whacky games here to make sure all the codes are exported Object.keys(CODES).forEach(function (code) { module.exports[code] = CODES[code] if (code === 'LDAP_SUCCESS') { return } let err = '' let msg = '' const pieces = code.split('_').slice(1) for (let i = 0; i < pieces.length; i++) { const lc = pieces[i].toLowerCase() const key = lc.charAt(0).toUpperCase() + lc.slice(1) err += key msg += key + ((i + 1) < pieces.length ? ' ' : '') } if (!/\w+Error$/.test(err)) { err += 'Error' } // At this point LDAP_OPERATIONS_ERROR is now OperationsError in $err // and 'Operations Error' in $msg module.exports[err] = function (message, dn, caller) { LDAPError.call(this, message, dn, caller || module.exports[err]) } module.exports[err].constructor = module.exports[err] util.inherits(module.exports[err], LDAPError) Object.defineProperties(module.exports[err].prototype, { name: { get: function getName () { return err }, configurable: false }, code: { get: function getCode () { return CODES[code] }, configurable: false } }) ERRORS[CODES[code]] = { err: err, message: msg } }) module.exports.getError = function (res) { assert.ok(res instanceof LDAPResult, 'res (LDAPResult) required') const errObj = ERRORS[res.status] const E = module.exports[errObj.err] return new E(res.errorMessage || errObj.message, res.matchedDN || null, module.exports.getError) } module.exports.getMessage = function (code) { assert.number(code, 'code (number) required') const errObj = ERRORS[code] return (errObj && errObj.message ? errObj.message : '') } /// --- Custom application errors function ConnectionError (message) { LDAPError.call(this, message, null, ConnectionError) } util.inherits(ConnectionError, LDAPError) module.exports.ConnectionError = ConnectionError Object.defineProperties(ConnectionError.prototype, { name: { get: function () { return 'ConnectionError' }, configurable: false } }) function AbandonedError (message) { LDAPError.call(this, message, null, AbandonedError) } util.inherits(AbandonedError, LDAPError) module.exports.AbandonedError = AbandonedError Object.defineProperties(AbandonedError.prototype, { name: { get: function () { return 'AbandonedError' }, configurable: false } }) function TimeoutError (message) { LDAPError.call(this, message, null, TimeoutError) } util.inherits(TimeoutError, LDAPError) module.exports.TimeoutError = TimeoutError Object.defineProperties(TimeoutError.prototype, { name: { get: function () { return 'TimeoutError' }, configurable: false } }) node-ldapjs-2.3.3/lib/filters/000077500000000000000000000000001424777304200161475ustar00rootroot00000000000000node-ldapjs-2.3.3/lib/filters/and_filter.js000066400000000000000000000010161424777304200206120ustar00rootroot00000000000000// 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 } node-ldapjs-2.3.3/lib/filters/approx_filter.js000066400000000000000000000013571424777304200213710ustar00rootroot00000000000000// 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 } node-ldapjs-2.3.3/lib/filters/equality_filter.js000066400000000000000000000027511424777304200217140ustar00rootroot00000000000000// 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 } node-ldapjs-2.3.3/lib/filters/escape.js000066400000000000000000000016671424777304200177570ustar00rootroot00000000000000// 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 } } node-ldapjs-2.3.3/lib/filters/ext_filter.js000066400000000000000000000026641424777304200206620ustar00rootroot00000000000000// 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 } node-ldapjs-2.3.3/lib/filters/filter.js000066400000000000000000000024521424777304200177750ustar00rootroot00000000000000// 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 } node-ldapjs-2.3.3/lib/filters/ge_filter.js000066400000000000000000000014371424777304200204520ustar00rootroot00000000000000// 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 } node-ldapjs-2.3.3/lib/filters/index.js000066400000000000000000000126551424777304200176250ustar00rootroot00000000000000// 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 } node-ldapjs-2.3.3/lib/filters/le_filter.js000066400000000000000000000014071424777304200204540ustar00rootroot00000000000000// 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 } node-ldapjs-2.3.3/lib/filters/not_filter.js000066400000000000000000000007361424777304200206600ustar00rootroot00000000000000// 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) } node-ldapjs-2.3.3/lib/filters/or_filter.js000066400000000000000000000010071424777304200204700ustar00rootroot00000000000000// 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 } node-ldapjs-2.3.3/lib/filters/presence_filter.js000066400000000000000000000014321424777304200216560ustar00rootroot00000000000000// 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 } node-ldapjs-2.3.3/lib/filters/substr_filter.js000066400000000000000000000033101424777304200213710ustar00rootroot00000000000000// 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 } node-ldapjs-2.3.3/lib/index.js000066400000000000000000000035441424777304200161520ustar00rootroot00000000000000// Copyright 2011 Mark Cavage, Inc. All rights reserved. const logger = require('./logger') const client = require('./client') const Attribute = require('./attribute') const Change = require('./change') const Protocol = require('./protocol') const Server = require('./server') const controls = require('./controls') const persistentSearch = require('./persistent_search') const dn = require('./dn') const errors = require('./errors') const filters = require('./filters') const messages = require('./messages') const url = require('./url') const hasOwnProperty = (target, val) => Object.prototype.hasOwnProperty.call(target, val) /// --- API module.exports = { Client: client.Client, createClient: client.createClient, Server: Server, createServer: function (options) { if (options === undefined) { options = {} } if (typeof (options) !== 'object') { throw new TypeError('options (object) required') } if (!options.log) { options.log = logger } return new Server(options) }, Attribute: Attribute, Change: Change, dn: dn, DN: dn.DN, RDN: dn.RDN, parseDN: dn.parse, persistentSearch: persistentSearch, PersistentSearchCache: persistentSearch.PersistentSearchCache, filters: filters, parseFilter: filters.parseString, url: url, parseURL: url.parse } /// --- Export all the childrenz let k for (k in Protocol) { if (hasOwnProperty(Protocol, k)) { module.exports[k] = Protocol[k] } } for (k in messages) { if (hasOwnProperty(messages, k)) { module.exports[k] = messages[k] } } for (k in controls) { if (hasOwnProperty(controls, k)) { module.exports[k] = controls[k] } } for (k in filters) { if (hasOwnProperty(filters, k)) { if (k !== 'parse' && k !== 'parseString') { module.exports[k] = filters[k] } } } for (k in errors) { if (hasOwnProperty(errors, k)) { module.exports[k] = errors[k] } } node-ldapjs-2.3.3/lib/logger.js000066400000000000000000000001771424777304200163210ustar00rootroot00000000000000'use strict' const logger = require('abstract-logging') logger.child = function () { return logger } module.exports = logger node-ldapjs-2.3.3/lib/messages/000077500000000000000000000000001424777304200163065ustar00rootroot00000000000000node-ldapjs-2.3.3/lib/messages/abandon_request.js000066400000000000000000000033401424777304200220160ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/abandon_response.js000066400000000000000000000013421424777304200221640ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/add_request.js000066400000000000000000000071351424777304200211520ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/add_response.js000066400000000000000000000007271424777304200213200ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/bind_request.js000066400000000000000000000035441424777304200213360ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/bind_response.js000066400000000000000000000007331424777304200215010ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/compare_request.js000066400000000000000000000031331424777304200220420ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/compare_response.js000066400000000000000000000013521424777304200222110ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/del_request.js000066400000000000000000000024501424777304200211610ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/del_response.js000066400000000000000000000007431424777304200213320ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/ext_request.js000066400000000000000000000057521424777304200212250ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/ext_response.js000066400000000000000000000041371424777304200213670ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/index.js000066400000000000000000000041021424777304200177500ustar00rootroot00000000000000// Copyright 2011 Mark Cavage, Inc. All rights reserved. const LDAPMessage = require('./message') const LDAPResult = require('./result') const Parser = require('./parser') const AbandonRequest = require('./abandon_request') const AbandonResponse = require('./abandon_response') const AddRequest = require('./add_request') const AddResponse = require('./add_response') const BindRequest = require('./bind_request') const BindResponse = require('./bind_response') const CompareRequest = require('./compare_request') const CompareResponse = require('./compare_response') const DeleteRequest = require('./del_request') const DeleteResponse = require('./del_response') const ExtendedRequest = require('./ext_request') const ExtendedResponse = require('./ext_response') const ModifyRequest = require('./modify_request') const ModifyResponse = require('./modify_response') const ModifyDNRequest = require('./moddn_request') const ModifyDNResponse = require('./moddn_response') const SearchRequest = require('./search_request') const SearchEntry = require('./search_entry') const SearchReference = require('./search_reference') const SearchResponse = require('./search_response') const UnbindRequest = require('./unbind_request') const UnbindResponse = require('./unbind_response') /// --- API module.exports = { LDAPMessage: LDAPMessage, LDAPResult: LDAPResult, Parser: Parser, AbandonRequest: AbandonRequest, AbandonResponse: AbandonResponse, AddRequest: AddRequest, AddResponse: AddResponse, BindRequest: BindRequest, BindResponse: BindResponse, CompareRequest: CompareRequest, CompareResponse: CompareResponse, DeleteRequest: DeleteRequest, DeleteResponse: DeleteResponse, ExtendedRequest: ExtendedRequest, ExtendedResponse: ExtendedResponse, ModifyRequest: ModifyRequest, ModifyResponse: ModifyResponse, ModifyDNRequest: ModifyDNRequest, ModifyDNResponse: ModifyDNResponse, SearchRequest: SearchRequest, SearchEntry: SearchEntry, SearchReference: SearchReference, SearchResponse: SearchResponse, UnbindRequest: UnbindRequest, UnbindResponse: UnbindResponse } node-ldapjs-2.3.3/lib/messages/message.js000066400000000000000000000046211424777304200202730ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/moddn_request.js000066400000000000000000000041671424777304200215250ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/moddn_response.js000066400000000000000000000007511424777304200216660ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/modify_request.js000066400000000000000000000034031424777304200217030ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/modify_response.js000066400000000000000000000007431424777304200220550ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/parser.js000066400000000000000000000121371424777304200201440ustar00rootroot00000000000000// Copyright 2011 Mark Cavage, Inc. All rights reserved. const EventEmitter = require('events').EventEmitter const util = require('util') const assert = require('assert-plus') const asn1 = require('asn1') // var VError = require('verror').VError const logger = require('../logger') const AbandonRequest = require('./abandon_request') const AddRequest = require('./add_request') const AddResponse = require('./add_response') const BindRequest = require('./bind_request') const BindResponse = require('./bind_response') const CompareRequest = require('./compare_request') const CompareResponse = require('./compare_response') const DeleteRequest = require('./del_request') const DeleteResponse = require('./del_response') const ExtendedRequest = require('./ext_request') const ExtendedResponse = require('./ext_response') const ModifyRequest = require('./modify_request') const ModifyResponse = require('./modify_response') const ModifyDNRequest = require('./moddn_request') const ModifyDNResponse = require('./moddn_response') const SearchRequest = require('./search_request') const SearchEntry = require('./search_entry') const SearchReference = require('./search_reference') const SearchResponse = require('./search_response') const UnbindRequest = require('./unbind_request') // var UnbindResponse = require('./unbind_response') const LDAPResult = require('./result') // var Message = require('./message') const Protocol = require('../protocol') /// --- Globals // var Ber = asn1.Ber const BerReader = asn1.BerReader /// --- API function Parser (options = {}) { assert.object(options) EventEmitter.call(this) this.buffer = null this.log = options.log || logger } util.inherits(Parser, EventEmitter) Parser.prototype.write = function (data) { if (!data || !Buffer.isBuffer(data)) { throw new TypeError('data (buffer) required') } let nextMessage = null const self = this function end () { if (nextMessage) { return self.write(nextMessage) } return true } self.buffer = (self.buffer ? Buffer.concat([self.buffer, data]) : data) const ber = new BerReader(self.buffer) let foundSeq = false try { foundSeq = ber.readSequence() } catch (e) { this.emit('error', e) } if (!foundSeq || ber.remain < ber.length) { // ENOTENOUGH return false } else if (ber.remain > ber.length) { // ETOOMUCH // This is sort of ugly, but allows us to make miminal copies nextMessage = self.buffer.slice(ber.offset + ber.length) ber._size = ber.offset + ber.length assert.equal(ber.remain, ber.length) } // If we're here, ber holds the message, and nextMessage is temporarily // pointing at the next sequence of data (if it exists) self.buffer = null let message try { // Bail here if peer isn't speaking protocol at all message = this.getMessage(ber) if (!message) { return end() } message.parse(ber) } catch (e) { this.emit('error', e, message) return false } this.emit('message', message) return end() } Parser.prototype.getMessage = function (ber) { assert.ok(ber) const self = this const messageID = ber.readInt() const type = ber.readSequence() let Message switch (type) { case Protocol.LDAP_REQ_ABANDON: Message = AbandonRequest break case Protocol.LDAP_REQ_ADD: Message = AddRequest break case Protocol.LDAP_REP_ADD: Message = AddResponse break case Protocol.LDAP_REQ_BIND: Message = BindRequest break case Protocol.LDAP_REP_BIND: Message = BindResponse break case Protocol.LDAP_REQ_COMPARE: Message = CompareRequest break case Protocol.LDAP_REP_COMPARE: Message = CompareResponse break case Protocol.LDAP_REQ_DELETE: Message = DeleteRequest break case Protocol.LDAP_REP_DELETE: Message = DeleteResponse break case Protocol.LDAP_REQ_EXTENSION: Message = ExtendedRequest break case Protocol.LDAP_REP_EXTENSION: Message = ExtendedResponse break case Protocol.LDAP_REQ_MODIFY: Message = ModifyRequest break case Protocol.LDAP_REP_MODIFY: Message = ModifyResponse break case Protocol.LDAP_REQ_MODRDN: Message = ModifyDNRequest break case Protocol.LDAP_REP_MODRDN: Message = ModifyDNResponse break case Protocol.LDAP_REQ_SEARCH: Message = SearchRequest break case Protocol.LDAP_REP_SEARCH_ENTRY: Message = SearchEntry break case Protocol.LDAP_REP_SEARCH_REF: Message = SearchReference break case Protocol.LDAP_REP_SEARCH: Message = SearchResponse break case Protocol.LDAP_REQ_UNBIND: Message = UnbindRequest break default: this.emit('error', new Error('Op 0x' + (type ? type.toString(16) : '??') + ' not supported'), new LDAPResult({ messageID: messageID, protocolOp: type || Protocol.LDAP_REP_EXTENSION })) return false } return new Message({ messageID: messageID, log: self.log }) } /// --- Exports module.exports = Parser node-ldapjs-2.3.3/lib/messages/result.js000066400000000000000000000055561424777304200201750ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/search_entry.js000066400000000000000000000105041424777304200213320ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/search_reference.js000066400000000000000000000040341424777304200221300ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/search_request.js000066400000000000000000000077401424777304200216710ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/search_response.js000066400000000000000000000101541424777304200220300ustar00rootroot00000000000000// Copyright 2011 Mark Cavage, Inc. All rights reserved. const assert = require('assert-plus') const util = require('util') const LDAPResult = require('./result') const SearchEntry = require('./search_entry') const SearchReference = require('./search_reference') const dtrace = require('../dtrace') const parseDN = require('../dn').parse const parseURL = require('../url').parse const Protocol = require('../protocol') /// --- API function SearchResponse (options) { options = options || {} assert.object(options) options.protocolOp = Protocol.LDAP_REP_SEARCH LDAPResult.call(this, options) this.attributes = options.attributes ? options.attributes.slice() : [] this.notAttributes = [] this.sentEntries = 0 } util.inherits(SearchResponse, LDAPResult) /** * Allows you to send a SearchEntry back to the client. * * @param {Object} entry an instance of SearchEntry. * @param {Boolean} nofiltering skip filtering notAttributes and '_' attributes. * Defaults to 'false'. */ SearchResponse.prototype.send = function (entry, nofiltering) { if (!entry || typeof (entry) !== 'object') { throw new TypeError('entry (SearchEntry) required') } if (nofiltering === undefined) { nofiltering = false } if (typeof (nofiltering) !== 'boolean') { throw new TypeError('noFiltering must be a boolean') } const self = this const savedAttrs = {} let save = null if (entry instanceof SearchEntry || entry instanceof SearchReference) { if (!entry.messageID) { entry.messageID = this.messageID } if (entry.messageID !== this.messageID) { throw new Error('SearchEntry messageID mismatch') } } else { if (!entry.attributes) { throw new Error('entry.attributes required') } const all = (self.attributes.indexOf('*') !== -1) Object.keys(entry.attributes).forEach(function (a) { const _a = a.toLowerCase() if (!nofiltering && _a.length && _a[0] === '_') { savedAttrs[a] = entry.attributes[a] delete entry.attributes[a] } else if (!nofiltering && self.notAttributes.indexOf(_a) !== -1) { savedAttrs[a] = entry.attributes[a] delete entry.attributes[a] } else if (all) { // do nothing } else if (self.attributes.length && self.attributes.indexOf(_a) === -1) { savedAttrs[a] = entry.attributes[a] delete entry.attributes[a] } }) save = entry entry = new SearchEntry({ objectName: typeof (save.dn) === 'string' ? parseDN(save.dn) : save.dn, messageID: self.messageID, log: self.log }) entry.fromObject(save) } try { this.log.debug('%s: sending: %j', this.connection.ldap.id, entry.json) this.connection.write(entry.toBer()) this.sentEntries++ if (self._dtraceOp && self._dtraceId) { dtrace.fire('server-search-entry', function () { const c = self.connection || { ldap: {} } return [ self._dtraceId || 0, (c.remoteAddress || ''), c.ldap.bindDN ? c.ldap.bindDN.toString() : '', (self.requestDN ? self.requestDN.toString() : ''), entry.objectName.toString(), entry.attributes.length ] }) } // Restore attributes Object.keys(savedAttrs).forEach(function (k) { save.attributes[k] = savedAttrs[k] }) } catch (e) { this.log.warn(e, '%s failure to write message %j', this.connection.ldap.id, this.json) } } SearchResponse.prototype.createSearchEntry = function (object) { assert.object(object) const entry = new SearchEntry({ messageID: this.messageID, log: this.log, objectName: object.objectName || object.dn }) entry.fromObject((object.attributes || object)) return entry } SearchResponse.prototype.createSearchReference = function (uris) { if (!uris) { throw new TypeError('uris ([string]) required') } if (!Array.isArray(uris)) { uris = [uris] } for (let i = 0; i < uris.length; i++) { if (typeof (uris[i]) === 'string') { uris[i] = parseURL(uris[i]) } } const self = this return new SearchReference({ messageID: self.messageID, log: self.log, uris: uris }) } /// --- Exports module.exports = SearchResponse node-ldapjs-2.3.3/lib/messages/unbind_request.js000066400000000000000000000022401424777304200216710ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/messages/unbind_response.js000066400000000000000000000030671424777304200220470ustar00rootroot00000000000000// 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 node-ldapjs-2.3.3/lib/persistent_search.js000066400000000000000000000060171424777304200205660ustar00rootroot00000000000000/// --- Globals // var parseDN = require('./dn').parse const EntryChangeNotificationControl = require('./controls').EntryChangeNotificationControl /// --- API // Cache used to store connected persistent search clients function PersistentSearch () { this.clientList = [] } PersistentSearch.prototype.addClient = function (req, res, callback) { if (typeof (req) !== 'object') { throw new TypeError('req must be an object') } if (typeof (res) !== 'object') { throw new TypeError('res must be an object') } if (callback && typeof (callback) !== 'function') { throw new TypeError('callback must be a function') } const log = req.log const client = {} client.req = req client.res = res log.debug('%s storing client', req.logId) this.clientList.push(client) log.debug('%s stored client', req.logId) log.debug('%s total number of clients %s', req.logId, this.clientList.length) if (callback) { callback(client) } } PersistentSearch.prototype.removeClient = function (req, res, callback) { if (typeof (req) !== 'object') { throw new TypeError('req must be an object') } if (typeof (res) !== 'object') { throw new TypeError('res must be an object') } if (callback && typeof (callback) !== 'function') { throw new TypeError('callback must be a function') } const log = req.log log.debug('%s removing client', req.logId) const client = {} client.req = req client.res = res // remove the client if it exists this.clientList.forEach(function (element, index, array) { if (element.req === client.req) { log.debug('%s removing client from list', req.logId) array.splice(index, 1) } }) log.debug('%s number of persistent search clients %s', req.logId, this.clientList.length) if (callback) { callback(client) } } function getOperationType (requestType) { switch (requestType) { case 'AddRequest': case 'add': return 1 case 'DeleteRequest': case 'delete': return 2 case 'ModifyRequest': case 'modify': return 4 case 'ModifyDNRequest': case 'modrdn': return 8 default: throw new TypeError('requestType %s, is an invalid request type', requestType) } } function getEntryChangeNotificationControl (req, obj) { // if we want to return a ECNC if (req.persistentSearch.value.returnECs) { const attrs = obj.attributes const value = {} value.changeType = getOperationType(attrs.changetype) // if it's a modDN request, fill in the previous DN if (value.changeType === 8 && attrs.previousDN) { value.previousDN = attrs.previousDN } value.changeNumber = attrs.changenumber return new EntryChangeNotificationControl({ value: value }) } else { return false } } function checkChangeType (req, requestType) { return (req.persistentSearch.value.changeTypes & getOperationType(requestType)) } /// --- Exports module.exports = { PersistentSearchCache: PersistentSearch, checkChangeType: checkChangeType, getEntryChangeNotificationControl: getEntryChangeNotificationControl } node-ldapjs-2.3.3/lib/protocol.js000066400000000000000000000020721424777304200166770ustar00rootroot00000000000000// 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 } node-ldapjs-2.3.3/lib/server.js000066400000000000000000000612321424777304200163470ustar00rootroot00000000000000// Copyright 2011 Mark Cavage, Inc. All rights reserved. const assert = require('assert') const EventEmitter = require('events').EventEmitter const net = require('net') const tls = require('tls') const util = require('util') // var asn1 = require('asn1') const VError = require('verror').VError const dn = require('./dn') const dtrace = require('./dtrace') const errors = require('./errors') const Protocol = require('./protocol') const Parser = require('./messages').Parser const AbandonResponse = require('./messages/abandon_response') const AddResponse = require('./messages/add_response') const BindResponse = require('./messages/bind_response') const CompareResponse = require('./messages/compare_response') const DeleteResponse = require('./messages/del_response') const ExtendedResponse = require('./messages/ext_response') // var LDAPResult = require('./messages/result') const ModifyResponse = require('./messages/modify_response') const ModifyDNResponse = require('./messages/moddn_response') const SearchRequest = require('./messages/search_request') const SearchResponse = require('./messages/search_response') const UnbindResponse = require('./messages/unbind_response') /// --- Globals // var Ber = asn1.Ber // var BerReader = asn1.BerReader const DN = dn.DN // var sprintf = util.format /// --- Helpers function mergeFunctionArgs (argv, start, end) { assert.ok(argv) if (!start) { start = 0 } if (!end) { end = argv.length } const handlers = [] for (let i = start; i < end; i++) { if (argv[i] instanceof Array) { const arr = argv[i] for (let j = 0; j < arr.length; j++) { if (!(arr[j] instanceof Function)) { throw new TypeError('Invalid argument type: ' + typeof (arr[j])) } handlers.push(arr[j]) } } else if (argv[i] instanceof Function) { handlers.push(argv[i]) } else { throw new TypeError('Invalid argument type: ' + typeof (argv[i])) } } return handlers } function getResponse (req) { assert.ok(req) let Response switch (req.protocolOp) { case Protocol.LDAP_REQ_BIND: Response = BindResponse break case Protocol.LDAP_REQ_ABANDON: Response = AbandonResponse break case Protocol.LDAP_REQ_ADD: Response = AddResponse break case Protocol.LDAP_REQ_COMPARE: Response = CompareResponse break case Protocol.LDAP_REQ_DELETE: Response = DeleteResponse break case Protocol.LDAP_REQ_EXTENSION: Response = ExtendedResponse break case Protocol.LDAP_REQ_MODIFY: Response = ModifyResponse break case Protocol.LDAP_REQ_MODRDN: Response = ModifyDNResponse break case Protocol.LDAP_REQ_SEARCH: Response = SearchResponse break case Protocol.LDAP_REQ_UNBIND: Response = UnbindResponse break default: return null } assert.ok(Response) const res = new Response({ messageID: req.messageID, log: req.log, attributes: ((req instanceof SearchRequest) ? req.attributes : undefined) }) res.connection = req.connection res.logId = req.logId return res } function defaultHandler (req, res, next) { assert.ok(req) assert.ok(res) assert.ok(next) res.matchedDN = req.dn.toString() res.errorMessage = 'Server method not implemented' res.end(errors.LDAP_OTHER) return next() } function defaultNoOpHandler (req, res, next) { assert.ok(req) assert.ok(res) assert.ok(next) res.end() return next() } function noSuffixHandler (req, res, next) { assert.ok(req) assert.ok(res) assert.ok(next) res.errorMessage = 'No tree found for: ' + req.dn.toString() res.end(errors.LDAP_NO_SUCH_OBJECT) return next() } function noExOpHandler (req, res, next) { assert.ok(req) assert.ok(res) assert.ok(next) res.errorMessage = req.requestName + ' not supported' res.end(errors.LDAP_PROTOCOL_ERROR) return next() } 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 /** * Constructs a new server that you can call .listen() on, in the various * forms node supports. You need to first assign some handlers to the various * LDAP operations however. * * The options object currently only takes a certificate/private key, and a * bunyan logger handle. * * This object exposes the following events: * - 'error' * - 'close' * * @param {Object} options (optional) parameterization object. * @throws {TypeError} on bad input. */ function Server (options) { if (options) { if (typeof (options) !== 'object') { throw new TypeError('options (object) required') } if (typeof (options.log) !== 'object') { throw new TypeError('options.log must be an object') } if (options.certificate || options.key) { if (!(options.certificate && options.key) || (typeof (options.certificate) !== 'string' && !Buffer.isBuffer(options.certificate)) || (typeof (options.key) !== 'string' && !Buffer.isBuffer(options.key))) { throw new TypeError('options.certificate and options.key ' + '(string or buffer) are both required for TLS') } } } else { options = {} } const self = this EventEmitter.call(this, options) this._chain = [] this.log = options.log this.strictDN = (options.strictDN !== undefined) ? options.strictDN : true const log = this.log function setupConnection (c) { assert.ok(c) if (c.type === 'unix') { c.remoteAddress = self.server.path c.remotePort = c.fd } else if (c.socket) { // TLS c.remoteAddress = c.socket.remoteAddress c.remotePort = c.socket.remotePort } const rdn = new dn.RDN({ cn: 'anonymous' }) c.ldap = { id: c.remoteAddress + ':' + c.remotePort, config: options, _bindDN: new DN([rdn]) } c.addListener('timeout', function () { log.trace('%s timed out', c.ldap.id) c.destroy() }) c.addListener('end', function () { log.trace('%s shutdown', c.ldap.id) }) c.addListener('error', function (err) { log.warn('%s unexpected connection error', c.ldap.id, err) self.emit('clientError', err) c.destroy() }) c.addListener('close', function (closeError) { log.trace('%s close; had_err=%j', c.ldap.id, closeError) c.end() }) c.ldap.__defineGetter__('bindDN', function () { return c.ldap._bindDN }) c.ldap.__defineSetter__('bindDN', function (val) { if (!(val instanceof DN)) { throw new TypeError('DN required') } c.ldap._bindDN = val return val }) return c } self.newConnection = function (conn) { // TODO: make `newConnection` available on the `Server` prototype // https://github.com/ldapjs/node-ldapjs/pull/727/files#r636572294 setupConnection(conn) log.trace('new connection from %s', conn.ldap.id) dtrace.fire('server-connection', function () { return [conn.remoteAddress] }) conn.parser = new Parser({ log: options.log }) conn.parser.on('message', function (req) { req.connection = conn req.logId = conn.ldap.id + '::' + req.messageID req.startTime = new Date().getTime() log.debug('%s: message received: req=%j', conn.ldap.id, req.json) const res = getResponse(req) if (!res) { log.warn('Unimplemented server method: %s', req.type) conn.destroy() return false } // parse string DNs for routing/etc try { switch (req.protocolOp) { case Protocol.LDAP_REQ_BIND: req.name = dn.parse(req.name) break case Protocol.LDAP_REQ_ADD: case Protocol.LDAP_REQ_COMPARE: case Protocol.LDAP_REQ_DELETE: req.entry = dn.parse(req.entry) break case Protocol.LDAP_REQ_MODIFY: req.object = dn.parse(req.object) break case Protocol.LDAP_REQ_MODRDN: req.entry = dn.parse(req.entry) // TODO: handle newRdn/Superior break case Protocol.LDAP_REQ_SEARCH: req.baseObject = dn.parse(req.baseObject) break default: break } } catch (e) { if (self.strictDN) { return res.end(errors.LDAP_INVALID_DN_SYNTAX) } } res.connection = conn res.logId = req.logId res.requestDN = req.dn const chain = self._getHandlerChain(req, res) let i = 0 return (function messageIIFE (err) { function sendError (sendErr) { res.status = sendErr.code || errors.LDAP_OPERATIONS_ERROR res.matchedDN = req.suffix ? req.suffix.toString() : '' res.errorMessage = sendErr.message || '' return res.end() } function after () { if (!self._postChain || !self._postChain.length) { return } function next () {} // stub out next for the post chain self._postChain.forEach(function (cb) { cb.call(self, req, res, next) }) } if (err) { log.trace('%s sending error: %s', req.logId, err.stack || err) self.emit('clientError', err) sendError(err) return after() } try { const next = messageIIFE if (chain.handlers[i]) { return chain.handlers[i++].call(chain.backend, req, res, next) } if (req.protocolOp === Protocol.LDAP_REQ_BIND && res.status === 0) { // 0 length == anonymous bind if (req.dn.length === 0 && req.credentials === '') { conn.ldap.bindDN = new DN([new dn.RDN({ cn: 'anonymous' })]) } else { conn.ldap.bindDN = req.dn } } // unbind clear bindDN for safety // conn should terminate on unbind (RFC4511 4.3) if (req.protocolOp === Protocol.LDAP_REQ_UNBIND && res.status === 0) { conn.ldap.bindDN = new DN([new dn.RDN({ cn: 'anonymous' })]) } return after() } catch (e) { if (!e.stack) { e.stack = e.toString() } log.error('%s uncaught exception: %s', req.logId, e.stack) return sendError(new errors.OperationsError(e.message)) } }()) }) conn.parser.on('error', function (err, message) { self.emit('error', new VError(err, 'Parser error for %s', conn.ldap.id)) if (!message) { return conn.destroy() } const res = getResponse(message) if (!res) { return conn.destroy() } res.status = 0x02 // protocol error res.errorMessage = err.toString() return conn.end(res.toBer()) }) conn.on('data', function (data) { log.trace('data on %s: %s', conn.ldap.id, util.inspect(data)) conn.parser.write(data) }) } // end newConnection this.routes = {} if ((options.cert || options.certificate) && options.key) { options.cert = options.cert || options.certificate this.server = tls.createServer(options, options.connectionRouter ? options.connectionRouter : self.newConnection) } else { this.server = net.createServer(options.connectionRouter ? options.connectionRouter : self.newConnection) } this.server.log = options.log this.server.ldap = { config: options } this.server.on('close', function () { self.emit('close') }) this.server.on('error', function (err) { self.emit('error', err) }) } util.inherits(Server, EventEmitter) Object.defineProperties(Server.prototype, { maxConnections: { get: function getMaxConnections () { return this.server.maxConnections }, set: function setMaxConnections (val) { this.server.maxConnections = val }, configurable: false }, connections: { get: function getConnections () { return this.server.connections }, configurable: false }, name: { get: function getName () { return 'LDAPServer' }, configurable: false }, url: { get: function getURL () { let str const addr = this.server.address() if (!addr) { return null } if (!addr.family) { str = 'ldapi://' str += this.host.replace(/\//g, '%2f') return str } if (this.server instanceof tls.Server) { str = 'ldaps://' } else { str = 'ldap://' } let host = this.host // Node 18 switched family from returning a string to returning a number // https://nodejs.org/api/net.html#serveraddress if (addr.family === 'IPv6' || addr.family === 6) { host = '[' + this.host + ']' } str += host + ':' + this.port return str }, configurable: false } }) module.exports = Server /** * Adds a handler (chain) for the LDAP add method. * * Note that this is of the form f(name, [function]) where the second...N * arguments can all either be functions or arrays of functions. * * @param {String} name the DN to mount this handler chain at. * @return {Server} this so you can chain calls. * @throws {TypeError} on bad input */ Server.prototype.add = function (name) { const args = Array.prototype.slice.call(arguments, 1) return this._mount(Protocol.LDAP_REQ_ADD, name, args) } /** * Adds a handler (chain) for the LDAP bind method. * * Note that this is of the form f(name, [function]) where the second...N * arguments can all either be functions or arrays of functions. * * @param {String} name the DN to mount this handler chain at. * @return {Server} this so you can chain calls. * @throws {TypeError} on bad input */ Server.prototype.bind = function (name) { const args = Array.prototype.slice.call(arguments, 1) return this._mount(Protocol.LDAP_REQ_BIND, name, args) } /** * Adds a handler (chain) for the LDAP compare method. * * Note that this is of the form f(name, [function]) where the second...N * arguments can all either be functions or arrays of functions. * * @param {String} name the DN to mount this handler chain at. * @return {Server} this so you can chain calls. * @throws {TypeError} on bad input */ Server.prototype.compare = function (name) { const args = Array.prototype.slice.call(arguments, 1) return this._mount(Protocol.LDAP_REQ_COMPARE, name, args) } /** * Adds a handler (chain) for the LDAP delete method. * * Note that this is of the form f(name, [function]) where the second...N * arguments can all either be functions or arrays of functions. * * @param {String} name the DN to mount this handler chain at. * @return {Server} this so you can chain calls. * @throws {TypeError} on bad input */ Server.prototype.del = function (name) { const args = Array.prototype.slice.call(arguments, 1) return this._mount(Protocol.LDAP_REQ_DELETE, name, args) } /** * Adds a handler (chain) for the LDAP exop method. * * Note that this is of the form f(name, [function]) where the second...N * arguments can all either be functions or arrays of functions. * * @param {String} name OID to assign this handler chain to. * @return {Server} this so you can chain calls. * @throws {TypeError} on bad input. */ Server.prototype.exop = function (name) { const args = Array.prototype.slice.call(arguments, 1) return this._mount(Protocol.LDAP_REQ_EXTENSION, name, args, true) } /** * Adds a handler (chain) for the LDAP modify method. * * Note that this is of the form f(name, [function]) where the second...N * arguments can all either be functions or arrays of functions. * * @param {String} name the DN to mount this handler chain at. * @return {Server} this so you can chain calls. * @throws {TypeError} on bad input */ Server.prototype.modify = function (name) { const args = Array.prototype.slice.call(arguments, 1) return this._mount(Protocol.LDAP_REQ_MODIFY, name, args) } /** * Adds a handler (chain) for the LDAP modifyDN method. * * Note that this is of the form f(name, [function]) where the second...N * arguments can all either be functions or arrays of functions. * * @param {String} name the DN to mount this handler chain at. * @return {Server} this so you can chain calls. * @throws {TypeError} on bad input */ Server.prototype.modifyDN = function (name) { const args = Array.prototype.slice.call(arguments, 1) return this._mount(Protocol.LDAP_REQ_MODRDN, name, args) } /** * Adds a handler (chain) for the LDAP search method. * * Note that this is of the form f(name, [function]) where the second...N * arguments can all either be functions or arrays of functions. * * @param {String} name the DN to mount this handler chain at. * @return {Server} this so you can chain calls. * @throws {TypeError} on bad input */ Server.prototype.search = function (name) { const args = Array.prototype.slice.call(arguments, 1) return this._mount(Protocol.LDAP_REQ_SEARCH, name, args) } /** * Adds a handler (chain) for the LDAP unbind method. * * This method is different than the others and takes no mount point, as unbind * is a connection-wide operation, not constrianed to part of the DIT. * * @return {Server} this so you can chain calls. * @throws {TypeError} on bad input */ Server.prototype.unbind = function () { const args = Array.prototype.slice.call(arguments, 0) return this._mount(Protocol.LDAP_REQ_UNBIND, 'unbind', args, true) } Server.prototype.use = function use () { const args = Array.prototype.slice.call(arguments) const chain = mergeFunctionArgs(args, 0, args.length) const self = this chain.forEach(function (c) { self._chain.push(c) }) } Server.prototype.after = function () { if (!this._postChain) { this._postChain = [] } const self = this mergeFunctionArgs(arguments).forEach(function (h) { self._postChain.push(h) }) } // All these just reexpose the requisite net.Server APIs Server.prototype.listen = function (port, host, callback) { if (typeof (port) !== 'number' && typeof (port) !== 'string') { throw new TypeError('port (number or path) required') } if (typeof (host) === 'function') { callback = host host = '0.0.0.0' } if (typeof (port) === 'string' && /^[0-9]+$/.test(port)) { // Disambiguate between string ports and file paths port = parseInt(port, 10) } const self = this function cbListen () { if (typeof (port) === 'number') { self.host = self.address().address self.port = self.address().port } else { self.host = port self.port = self.server.fd } if (typeof (callback) === 'function') { callback() } } if (typeof (port) === 'number') { return this.server.listen(port, host, cbListen) } else { return this.server.listen(port, cbListen) } } Server.prototype.listenFD = function (fd) { this.host = 'unix-domain-socket' this.port = fd return this.server.listenFD(fd) } Server.prototype.close = function (callback) { return this.server.close(callback) } Server.prototype.address = function () { return this.server.address() } Server.prototype.getConnections = function (callback) { return this.server.getConnections(callback) } Server.prototype._getRoute = function (_dn, backend) { assert.ok(dn) if (!backend) { backend = this } let name if (_dn instanceof dn.DN) { name = _dn.toString() } else { name = _dn } if (!this.routes[name]) { this.routes[name] = {} this.routes[name].backend = backend this.routes[name].dn = _dn // Force regeneration of the route key cache on next request this._routeKeyCache = null } return this.routes[name] } Server.prototype._sortedRouteKeys = function _sortedRouteKeys () { // The filtered/sorted route keys are cached to prevent needlessly // regenerating the list for every incoming request. if (!this._routeKeyCache) { const self = this const reversedRDNsToKeys = {} // Generate mapping of reversedRDNs(DN) -> routeKey Object.keys(this.routes).forEach(function (key) { const _dn = self.routes[key].dn // Ignore non-DN routes such as exop or unbind if (_dn instanceof dn.DN) { const reversed = _dn.clone() reversed.rdns.reverse() reversedRDNsToKeys[reversed.format()] = key } }) const output = [] // Reverse-sort on reversedRDS(DN) in order to output routeKey list. // This will place more specific DNs in front of their parents: // 1. dc=test, dc=domain, dc=sub // 2. dc=test, dc=domain // 3. dc=other, dc=foobar Object.keys(reversedRDNsToKeys).sort().reverse().forEach(function (_dn) { output.push(reversedRDNsToKeys[_dn]) }) this._routeKeyCache = output } return this._routeKeyCache } Server.prototype._getHandlerChain = function _getHandlerChain (req, res) { assert.ok(req) fireDTraceProbe(req, res) const self = this const routes = this.routes let route // check anonymous bind if (req.protocolOp === Protocol.LDAP_REQ_BIND && req.dn.toString() === '' && req.credentials === '') { return { backend: self, handlers: [defaultNoOpHandler] } } const op = '0x' + req.protocolOp.toString(16) // Special cases are exops, unbinds and abandons. Handle those first. if (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) { route = routes[req.requestName] if (route) { return { backend: route.backend, handlers: (route[op] ? route[op] : [noExOpHandler]) } } else { return { backend: self, handlers: [noExOpHandler] } } } else if (req.protocolOp === Protocol.LDAP_REQ_UNBIND) { route = routes.unbind if (route) { return { backend: route.backend, handlers: route[op] } } else { return { backend: self, handlers: [defaultNoOpHandler] } } } else if (req.protocolOp === Protocol.LDAP_REQ_ABANDON) { return { backend: self, handlers: [defaultNoOpHandler] } } // Otherwise, match via DN rules assert.ok(req.dn) const keys = this._sortedRouteKeys() let fallbackHandler = [noSuffixHandler] // invalid DNs in non-strict mode are routed to the default handler const testDN = (typeof (req.dn) === 'string') ? '' : req.dn for (let i = 0; i < keys.length; i++) { const suffix = keys[i] route = routes[suffix] assert.ok(route.dn) // Match a valid route or the route wildcard ('') if (route.dn.equals(testDN) || route.dn.parentOf(testDN) || suffix === '') { if (route[op]) { // We should be good to go. req.suffix = route.dn return { backend: route.backend, handlers: route[op] } } else { if (suffix === '') { break } else { // We found a valid suffix but not a valid operation. // There might be a more generic suffix with a legitimate operation. fallbackHandler = [defaultHandler] } } } } return { backend: self, handlers: fallbackHandler } } Server.prototype._mount = function (op, name, argv, notDN) { assert.ok(op) assert.ok(name !== undefined) assert.ok(argv) if (typeof (name) !== 'string') { throw new TypeError('name (string) required') } if (!argv.length) { throw new Error('at least one handler required') } let backend = this let index = 0 if (typeof (argv[0]) === 'object' && !Array.isArray(argv[0])) { backend = argv[0] index = 1 } const route = this._getRoute(notDN ? name : dn.parse(name), backend) const chain = this._chain.slice() argv.slice(index).forEach(function (a) { chain.push(a) }) route['0x' + op.toString(16)] = mergeFunctionArgs(chain) return this } node-ldapjs-2.3.3/lib/url.js000066400000000000000000000036321424777304200156430ustar00rootroot00000000000000'use strict' const querystring = require('querystring') const url = require('url') const dn = require('./dn') const filter = require('./filters/') module.exports = { parse: function (urlStr, parseDN) { let parsedURL try { parsedURL = new url.URL(urlStr) } catch (error) { throw new TypeError(urlStr + ' is an invalid LDAP url (scope)') } if (!parsedURL.protocol || !(parsedURL.protocol === 'ldap:' || parsedURL.protocol === 'ldaps:')) { throw new TypeError(urlStr + ' is an invalid LDAP url (protocol)') } const u = { protocol: parsedURL.protocol, hostname: parsedURL.hostname, port: parsedURL.port, pathname: parsedURL.pathname, search: parsedURL.search, href: parsedURL.href } u.secure = (u.protocol === 'ldaps:') if (!u.hostname) { u.hostname = 'localhost' } if (!u.port) { u.port = (u.secure ? 636 : 389) } else { u.port = parseInt(u.port, 10) } if (u.pathname) { u.pathname = querystring.unescape(u.pathname.substr(1)) u.DN = parseDN ? dn.parse(u.pathname) : u.pathname } if (u.search) { u.attributes = [] const tmp = u.search.substr(1).split('?') if (tmp && tmp.length) { if (tmp[0]) { tmp[0].split(',').forEach(function (a) { u.attributes.push(querystring.unescape(a.trim())) }) } } if (tmp[1]) { if (tmp[1] !== 'base' && tmp[1] !== 'one' && tmp[1] !== 'sub') { throw new TypeError(urlStr + ' is an invalid LDAP url (scope)') } u.scope = tmp[1] } if (tmp[2]) { u.filter = querystring.unescape(tmp[2]) } if (tmp[3]) { u.extensions = querystring.unescape(tmp[3]) } if (!u.scope) { u.scope = 'base' } if (!u.filter) { u.filter = filter.parseString('(objectclass=*)') } else { u.filter = filter.parseString(u.filter) } } return u } } node-ldapjs-2.3.3/package.json000066400000000000000000000031641424777304200162230ustar00rootroot00000000000000{ "originalAuthor": "Mark Cavage ", "name": "ldapjs", "homepage": "http://ldapjs.org", "description": "LDAP client and server APIs", "version": "2.3.3", "license": "MIT", "repository": { "type": "git", "url": "git://github.com/ldapjs/node-ldapjs.git" }, "main": "lib/index.js", "directories": { "lib": "./lib" }, "engines": { "node": ">=10.13.0" }, "dependencies": { "abstract-logging": "^2.0.0", "asn1": "^0.2.4", "assert-plus": "^1.0.0", "backoff": "^2.5.0", "ldap-filter": "^0.3.3", "once": "^1.4.0", "vasync": "^2.2.0", "verror": "^1.8.1" }, "devDependencies": { "eslint": "^7.20.0", "eslint-config-standard": "^16.0.2", "eslint-plugin-import": "^2.22.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.0", "front-matter": "^4.0.2", "get-port": "^5.1.1", "highlight.js": "^11.0.1", "husky": "^4.2.5", "marked": "^4.0.0", "tap": "15.2.3" }, "scripts": { "test": "tap --no-cov -R terse", "test:ci": "tap --coverage-report=lcovonly -R terse", "test:cov": "tap -R terse", "test:cov:html": "tap --coverage-report=html -R terse", "test:watch": "tap -n -w --no-coverage-report -R terse", "test:integration": "tap --no-cov -R terse 'test-integration/**/*.test.js'", "test:integration:local": "docker-compose up -d && npm run test:integration && docker-compose down", "lint": "eslint . --fix", "lint:ci": "eslint .", "docs": "node scripts/build-docs.js" }, "husky": { "hooks": { "pre-commit": "npm run lint:ci && npm run test" } } } node-ldapjs-2.3.3/scripts/000077500000000000000000000000001424777304200154205ustar00rootroot00000000000000node-ldapjs-2.3.3/scripts/build-docs.js000066400000000000000000000063141424777304200200070ustar00rootroot00000000000000const fs = require('fs/promises') const path = require('path') const { marked } = require('marked') const fm = require('front-matter') const { highlight } = require('highlight.js') marked.use({ highlight: (code, lang) => { if (lang) { return highlight(code, { language: lang }).value } return code } }) function tocHTML (toc) { let html = '
    \n' for (const li of toc) { html += '
  • \n' html += `\n` if (li.children && li.children.length > 0) { html += tocHTML(li.children) } html += '
  • \n' } html += '
\n' return html } function markdownTOC (markdown) { const tokens = marked.lexer(markdown) const slugger = new marked.Slugger() const toc = [] let currentHeading let ignoreFirst = true for (const token of tokens) { if (token.type === 'heading') { if (token.depth === 1) { if (ignoreFirst) { ignoreFirst = false continue } currentHeading = { text: token.text, slug: slugger.slug(token.text), children: [] } toc.push(currentHeading) } else if (token.depth === 2) { if (!currentHeading) { continue } currentHeading.children.push({ text: token.text, slug: slugger.slug(token.text) }) } } } return { toc: tocHTML(toc), html: marked.parser(tokens) } } function createHTML (template, text) { const { attributes, body } = fm(text) const { toc, html } = markdownTOC(body) attributes.toc_html = toc attributes.content = html for (const prop in attributes) { template = template.replace(new RegExp(`%\\(${prop}\\)s`, 'ig'), attributes[prop]) } return template } async function copyRecursive (src, dest) { const stats = await fs.stat(src) const isDirectory = stats.isDirectory() if (isDirectory) { await fs.mkdir(dest) const files = await fs.readdir(src) for (const file of files) { await copyRecursive(path.join(src, file), path.join(dest, file)) } } else { await fs.copyFile(src, dest) } } async function createDocs () { const docs = path.resolve(__dirname, '..', 'docs') const dist = path.resolve(__dirname, '..', 'public') const branding = path.join(docs, 'branding') const src = path.join(branding, 'public') try { await fs.rm(dist, { recursive: true }) } catch (ex) { if (ex.code !== 'ENOENT') { throw ex } } await copyRecursive(src, dist) const highlightjsStyles = path.resolve(__dirname, '..', 'node_modules', 'highlight.js', 'styles') await fs.copyFile(path.join(highlightjsStyles, 'default.css'), path.join(dist, 'media', 'css', 'highlight.css')) const template = await fs.readFile(path.join(branding, 'template.html'), { encoding: 'utf8' }) const files = await fs.readdir(docs) for (const file of files) { if (!file.endsWith('.md')) { continue } const text = await fs.readFile(path.join(docs, file), { encoding: 'utf8' }) const html = createHTML(template, text) await fs.writeFile(path.join(dist, file.replace(/md$/, 'html')), html) } } createDocs().catch(ex => { console.error(ex) process.exitCode = 1 }) node-ldapjs-2.3.3/test-integration/000077500000000000000000000000001424777304200172315ustar00rootroot00000000000000node-ldapjs-2.3.3/test-integration/.eslintrc.js000066400000000000000000000000731424777304200214700ustar00rootroot00000000000000module.exports = { rules: { 'no-shadow': 'off' } } node-ldapjs-2.3.3/test-integration/client/000077500000000000000000000000001424777304200205075ustar00rootroot00000000000000node-ldapjs-2.3.3/test-integration/client/connect.test.js000066400000000000000000000007701424777304200234600ustar00rootroot00000000000000'use strict' const tap = require('tap') const ldapjs = require('../../lib') const SCHEME = process.env.SCHEME || 'ldap' const HOST = process.env.HOST || '127.0.0.1' const PORT = process.env.PORT || 389 const baseURL = `${SCHEME}://${HOST}:${PORT}` tap.test('connects to a server', t => { t.plan(2) const client = ldapjs.createClient({ url: baseURL }) client.bind('cn=Philip J. Fry,ou=people,dc=planetexpress,dc=com', 'fry', (err) => { t.error(err) t.pass() client.unbind() }) }) node-ldapjs-2.3.3/test-integration/client/issues.test.js000066400000000000000000000052431424777304200233420ustar00rootroot00000000000000'use strict' const tap = require('tap') const ldapjs = require('../../lib') const SCHEME = process.env.SCHEME || 'ldap' const HOST = process.env.HOST || '127.0.0.1' const PORT = process.env.PORT || 389 const baseURL = `${SCHEME}://${HOST}:${PORT}` tap.test('modifyDN with long name (issue #480)', t => { const longStr = 'a292979f2c86d513d48bbb9786b564b3c5228146e5ba46f404724e322544a7304a2b1049168803a5485e2d57a544c6a0d860af91330acb77e5907a9e601ad1227e80e0dc50abe963b47a004f2c90f570450d0e920d15436fdc771e3bdac0487a9735473ed3a79361d1778d7e53a7fb0e5f01f97a75ef05837d1d5496fc86968ff47fcb64' const targetDN = 'cn=Turanga Leela,ou=people,dc=planetexpress,dc=com' const client = ldapjs.createClient({ url: baseURL }) client.bind('cn=admin,dc=planetexpress,dc=com', 'GoodNewsEveryone', bindHandler) function bindHandler (err) { t.error(err) client.modifyDN( targetDN, `cn=${longStr},ou=people,dc=planetexpress,dc=com`, modifyHandler ) } function modifyHandler (err, res) { t.error(err) t.ok(res) t.equal(res.status, 0) client.modifyDN( `cn=${longStr},ou=people,dc=planetexpress,dc=com`, targetDN, (err) => { t.error(err) client.unbind(t.end) } ) } }) tap.test('whois works correctly (issue #370)', t => { const client = ldapjs.createClient({ url: baseURL }) client.bind('cn=Philip J. Fry,ou=people,dc=planetexpress,dc=com', 'fry', (err) => { t.error(err) client.exop('1.3.6.1.4.1.4203.1.11.3', (err, value, res) => { t.error(err) t.ok(value) t.is(value, 'dn:cn=Philip J. Fry,ou=people,dc=planetexpress,dc=com') t.ok(res) t.is(res.status, 0) client.unbind(t.end) }) }) }) tap.test('can access large groups (issue #582)', t => { const client = ldapjs.createClient({ url: baseURL }) client.bind('cn=admin,dc=planetexpress,dc=com ', 'GoodNewsEveryone', (err) => { t.error(err) const searchOpts = { scope: 'sub', filter: '(&(objectClass=group)(cn=large_group))' } client.search('ou=large_ou,dc=planetexpress,dc=com', searchOpts, (err, response) => { t.error(err) const results = [] response.on('searchEntry', (entry) => { results.push(entry) }) response.on('error', t.error) response.on('end', (result) => { t.is(result.status, 0) t.is(results.length === 1, true) t.ok(results[0].attributes) const memberAttr = results[0].attributes.find(a => a.type === 'member') t.ok(memberAttr) t.ok(memberAttr.vals) t.type(memberAttr.vals, Array) t.is(memberAttr.vals.length, 2000) client.unbind(t.end) }) }) }) }) node-ldapjs-2.3.3/test/000077500000000000000000000000001424777304200147105ustar00rootroot00000000000000node-ldapjs-2.3.3/test/.eslintrc.js000066400000000000000000000000731424777304200171470ustar00rootroot00000000000000module.exports = { rules: { 'no-shadow': 'off' } } node-ldapjs-2.3.3/test/attribute.test.js000066400000000000000000000063471424777304200202410ustar00rootroot00000000000000'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() }) node-ldapjs-2.3.3/test/change.test.js000066400000000000000000000125621424777304200174570ustar00rootroot00000000000000'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() }) node-ldapjs-2.3.3/test/client.test.js000066400000000000000000001325611424777304200175120ustar00rootroot00000000000000'use strict' const util = require('util') const assert = require('assert') const tap = require('tap') const vasync = require('vasync') const getPort = require('get-port') const { getSock, uuid } = require('./utils') const ldap = require('../lib') const { Attribute, Change } = ldap const SUFFIX = 'dc=test' const LDAP_CONNECT_TIMEOUT = process.env.LDAP_CONNECT_TIMEOUT || 0 const BIND_DN = 'cn=root' const BIND_PW = 'secret' tap.beforeEach((t) => { return new Promise(resolve => { t.context.socketPath = getSock() t.context.server = ldap.createServer() const server = t.context.server server.bind(BIND_DN, function (req, res, next) { if (req.credentials !== BIND_PW) { return next(new ldap.InvalidCredentialsError('Invalid password')) } res.end() return next() }) server.add(SUFFIX, function (req, res, next) { res.end() return next() }) server.compare(SUFFIX, function (req, res, next) { res.end(req.value === 'test') return next() }) server.del(SUFFIX, function (req, res, next) { res.end() return next() }) // LDAP whoami server.exop('1.3.6.1.4.1.4203.1.11.3', function (req, res, next) { res.value = 'u:xxyyz@EXAMPLE.NET' res.end() return next() }) server.modify(SUFFIX, function (req, res, next) { res.end() return next() }) server.modifyDN(SUFFIX, function (req, res, next) { res.end() return next() }) server.modifyDN('cn=issue-480', function (req, res, next) { assert(req.newRdn.toString().length > 132) res.end() return next() }) server.search('dc=slow', function (req, res, next) { res.send({ dn: 'dc=slow', attributes: { you: 'wish', this: 'was', faster: '.' } }) setTimeout(function () { res.end() next() }, 250) }) server.search('dc=timeout', function () { // Cause the client to timeout by not sending a response. }) server.search(SUFFIX, function (req, res, next) { if (req.dn.equals('cn=ref,' + SUFFIX)) { res.send(res.createSearchReference('ldap://localhost')) } else if (req.dn.equals('cn=bin,' + SUFFIX)) { res.send(res.createSearchEntry({ objectName: req.dn, attributes: { 'foo;binary': 'wr0gKyDCvCA9IMK+', gb18030: Buffer.from([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA]), objectclass: 'binary' } })) } else { const e = res.createSearchEntry({ objectName: req.dn, attributes: { cn: ['unit', 'test'], SN: 'testy' } }) res.send(e) res.send(e) } res.end() return next() }) server.search('cn=sizelimit', function (req, res, next) { const sizeLimit = 200 for (let i = 0; i < 1000; i++) { if (req.sizeLimit > 0 && i >= req.sizeLimit) { break } else if (i > sizeLimit) { res.end(ldap.LDAP_SIZE_LIMIT_EXCEEDED) return next() } res.send({ dn: util.format('o=%d, cn=sizelimit', i), attributes: { o: [i], objectclass: ['pagedResult'] } }) } res.end() return next() }) server.search('cn=paged', function (req, res, next) { const min = 0 const max = 1000 function sendResults (start, end) { start = (start < min) ? min : start end = (end > max || end < min) ? max : end let i for (i = start; i < end; i++) { res.send({ dn: util.format('o=%d, cn=paged', i), attributes: { o: [i], objectclass: ['pagedResult'] } }) } return i } let cookie = null let pageSize = 0 req.controls.forEach(function (control) { if (control.type === ldap.PagedResultsControl.OID) { pageSize = control.value.size cookie = control.value.cookie } }) if (cookie && Buffer.isBuffer(cookie)) { // Do simple paging let first = min if (cookie.length !== 0) { first = parseInt(cookie.toString(), 10) } const last = sendResults(first, first + pageSize) let resultCookie if (last < max) { resultCookie = Buffer.from(last.toString()) } else { resultCookie = Buffer.from('') } res.controls.push(new ldap.PagedResultsControl({ value: { size: pageSize, // correctness not required here cookie: resultCookie } })) res.end() next() } else { // don't allow non-paged searches for this test endpoint next(new ldap.UnwillingToPerformError()) } }) server.search('cn=sssvlv', function (req, res, next) { const min = 0 const max = 100 const results = [] let o = 'aa' for (let i = min; i < max; i++) { results.push({ dn: util.format('o=%s, cn=sssvlv', o), attributes: { o: [o], objectclass: ['sssvlvResult'] } }) o = ((parseInt(o, 36) + 1).toString(36)).replace(/0/g, 'a') } function sendResults (start, end, sortBy, sortDesc) { start = (start < min) ? min : start end = (end > max || end < min) ? max : end const sorted = results.sort((a, b) => { if (a.attributes[sortBy][0] < b.attributes[sortBy][0]) { return sortDesc ? 1 : -1 } else if (a.attributes[sortBy][0] > b.attributes[sortBy][0]) { return sortDesc ? -1 : 1 } return 0 }) for (let i = start; i < end; i++) { res.send(sorted[i]) } } let sortBy = null let sortDesc = null let afterCount = null let targetOffset = null req.controls.forEach(function (control) { if (control.type === ldap.ServerSideSortingRequestControl.OID) { sortBy = control.value[0].attributeType sortDesc = control.value[0].reverseOrder } if (control.type === ldap.VirtualListViewRequestControl.OID) { afterCount = control.value.afterCount targetOffset = control.value.targetOffset } }) if (sortBy) { if (afterCount && targetOffset) { sendResults(targetOffset - 1, (targetOffset + afterCount), sortBy, sortDesc) } else { sendResults(min, max, sortBy, sortDesc) } res.end() next() } else { next(new ldap.UnwillingToPerformError()) } }) server.search('cn=pagederr', function (req, res, next) { let cookie = null req.controls.forEach(function (control) { if (control.type === ldap.PagedResultsControl.OID) { cookie = control.value.cookie } }) if (cookie && Buffer.isBuffer(cookie) && cookie.length === 0) { // send first "page" res.send({ dn: util.format('o=result, cn=pagederr'), attributes: { o: 'result', objectclass: ['pagedResult'] } }) res.controls.push(new ldap.PagedResultsControl({ value: { size: 2, cookie: Buffer.from('a') } })) res.end() return next() } else { // send error instead of second page res.end(ldap.LDAP_SIZE_LIMIT_EXCEEDED) return next() } }) server.search('dc=empty', function (req, res, next) { res.send({ dn: 'dc=empty', attributes: { member: [], 'member;range=0-1': ['cn=user1, dc=empty', 'cn=user2, dc=empty'] } }) res.end() return next() }) server.search('cn=busy', function (req, res, next) { next(new ldap.BusyError('too much to do')) }) server.search('', function (req, res, next) { if (req.dn.toString() === '') { res.send({ dn: '', attributes: { objectclass: ['RootDSE', 'top'] } }) res.end() } else { // Turn away any other requests (since '' is the fallthrough route) res.errorMessage = 'No tree found for: ' + req.dn.toString() res.end(ldap.LDAP_NO_SUCH_OBJECT) } return next() }) server.unbind(function (req, res, next) { res.end() return next() }) server.listen(t.context.socketPath, function () { const client = ldap.createClient({ connectTimeout: parseInt(LDAP_CONNECT_TIMEOUT, 10), socketPath: t.context.socketPath }) t.context.client = client client.on('connect', () => resolve()) }) }) }) tap.afterEach((t) => { return new Promise(resolve => { t.context.client.unbind((err) => { t.error(err) t.context.server.close(() => resolve()) }) }) }) tap.test('createClient', t => { t.test('requires an options object', async t => { const match = /options.+required/ t.throws(() => ldap.createClient(), match) t.throws(() => ldap.createClient([]), match) t.throws(() => ldap.createClient(''), match) t.throws(() => ldap.createClient(42), match) }) t.test('url must be a string or array', async t => { const match = /options\.url \(string\|array\) required/ t.throws(() => ldap.createClient({ url: {} }), match) t.throws(() => ldap.createClient({ url: 42 }), match) }) t.test('socketPath must be a string', async t => { const match = /options\.socketPath must be a string/ t.throws(() => ldap.createClient({ socketPath: {} }), match) t.throws(() => ldap.createClient({ socketPath: [] }), match) t.throws(() => ldap.createClient({ socketPath: 42 }), match) }) t.test('cannot supply both url and socketPath', async t => { t.throws( () => ldap.createClient({ url: 'foo', socketPath: 'bar' }), /options\.url \^ options\.socketPath \(String\) required/ ) }) t.test('must supply at least url or socketPath', async t => { t.throws( () => ldap.createClient({}), /options\.url \^ options\.socketPath \(String\) required/ ) }) t.test('exception from bad createClient parameter (issue #418)', t => { try { // This port number is totally invalid. It will cause the URL parser // to throw an exception that should be caught. ldap.createClient({ url: 'ldap://127.0.0.1:13891389' }) } catch (error) { t.ok(error) t.end() } }) t.test('url array is correctly assigned', async t => { getPort().then(function (unusedPortNumber) { const client = ldap.createClient({ url: [ `ldap://127.0.0.1:${unusedPortNumber}`, `ldap://127.0.0.2:${unusedPortNumber}` ], connectTimeout: 1 }) client.on('connectTimeout', () => {}) client.on('connectError', () => {}) client.on('connectRefused', () => {}) t.equal(client.urls.length, 2) }) }) // TODO: this test is really flaky. It would be better if we could validate // the options _withouth_ having to connect to a server. // t.test('attaches a child function to logger', async t => { // /* eslint-disable-next-line */ // let client // const logger = Object.create(require('abstract-logging')) // const socketPath = getSock() // const server = ldap.createServer() // server.listen(socketPath, () => {}) // t.teardown(() => { // client.unbind(() => server.close()) // }) // client = ldap.createClient({ socketPath, log: logger }) // t.ok(logger.child) // t.ok(typeof client.log.child === 'function') // }) t.end() }) tap.test('simple bind failure', function (t) { t.context.client.bind(BIND_DN, uuid(), function (err, res) { t.ok(err) t.notOk(res) t.ok(err instanceof ldap.InvalidCredentialsError) t.ok(err instanceof Error) t.ok(err.dn) t.ok(err.message) t.ok(err.stack) t.end() }) }) tap.test('simple bind success', function (t) { t.context.client.bind(BIND_DN, BIND_PW, function (err, res) { t.error(err) t.ok(res) t.equal(res.status, 0) t.end() }) }) tap.test('simple anonymous bind (empty credentials)', function (t) { t.context.client.bind('', '', function (err, res) { t.error(err) t.ok(res) t.equal(res.status, 0) t.end() }) }) tap.test('auto-bind bad credentials', function (t) { const clt = ldap.createClient({ socketPath: t.context.socketPath, bindDN: BIND_DN, bindCredentials: 'totallybogus' }) clt.once('error', function (err) { t.equal(err.code, ldap.LDAP_INVALID_CREDENTIALS) t.ok(clt._socket.destroyed, 'expect socket to be destroyed') clt.destroy() t.end() }) }) tap.test('auto-bind success', function (t) { const clt = ldap.createClient({ socketPath: t.context.socketPath, bindDN: BIND_DN, bindCredentials: BIND_PW }) clt.once('connect', function () { t.ok(clt) clt.destroy() t.end() }) }) tap.test('add success', function (t) { const attrs = [ new Attribute({ type: 'cn', vals: ['test'] }) ] t.context.client.add('cn=add, ' + SUFFIX, attrs, function (err, res) { t.error(err) t.ok(res) t.equal(res.status, 0) t.end() }) }) tap.test('add success with object', function (t) { const entry = { cn: ['unit', 'add'], sn: 'test' } t.context.client.add('cn=add, ' + SUFFIX, entry, function (err, res) { t.error(err) t.ok(res) t.equal(res.status, 0) t.end() }) }) tap.test('add buffer', function (t) { const { BerReader } = require('asn1') const dn = `cn=add, ${SUFFIX}` const attribute = 'thumbnailPhoto' const binary = 0xa5 const entry = { [attribute]: Buffer.from([binary]) } const write = t.context.client._socket.write t.context.client._socket.write = (data, encoding, cb) => { const reader = new BerReader(data) t.equal(data.byteLength, 49) t.ok(reader.readSequence()) t.equal(reader.readInt(), 0x1) t.equal(reader.readSequence(), 0x68) t.equal(reader.readString(), dn) t.ok(reader.readSequence()) t.ok(reader.readSequence()) t.equal(reader.readString(), attribute) t.equal(reader.readSequence(), 0x31) t.equal(reader.readByte(), 0x4) t.equal(reader.readByte(), 1) t.equal(reader.readByte(), binary) t.context.client._socket.write = write t.context.client._socket.write(data, encoding, cb) } t.context.client.add(dn, entry, function (err, res) { t.error(err) t.ok(res) t.equal(res.status, 0) t.end() }) }) tap.test('compare success', function (t) { t.context.client.compare('cn=compare, ' + SUFFIX, 'cn', 'test', function (err, matched, res) { t.error(err) t.ok(matched) t.ok(res) t.end() }) }) tap.test('compare false', function (t) { t.context.client.compare('cn=compare, ' + SUFFIX, 'cn', 'foo', function (err, matched, res) { t.error(err) t.notOk(matched) t.ok(res) t.end() }) }) tap.test('compare bad suffix', function (t) { t.context.client.compare('cn=' + uuid(), 'cn', 'foo', function (err, matched, res) { t.ok(err) t.ok(err instanceof ldap.NoSuchObjectError) t.notOk(matched) t.notOk(res) t.end() }) }) tap.test('delete success', function (t) { t.context.client.del('cn=delete, ' + SUFFIX, function (err, res) { t.error(err) t.ok(res) t.end() }) }) tap.test('delete with control (GH-212)', function (t) { const control = new ldap.Control({ type: '1.2.3.4', criticality: false }) t.context.client.del('cn=delete, ' + SUFFIX, control, function (err, res) { t.error(err) t.ok(res) t.end() }) }) tap.test('exop success', function (t) { t.context.client.exop('1.3.6.1.4.1.4203.1.11.3', function (err, value, res) { t.error(err) t.ok(value) t.ok(res) t.equal(value, 'u:xxyyz@EXAMPLE.NET') t.end() }) }) tap.test('exop invalid', function (t) { t.context.client.exop('1.2.3.4', function (err, res) { t.ok(err) t.ok(err instanceof ldap.ProtocolError) t.notOk(res) t.end() }) }) tap.test('bogus exop (GH-17)', function (t) { t.context.client.exop('cn=root', function (err) { t.ok(err) t.end() }) }) tap.test('modify success', function (t) { const change = new Change({ type: 'Replace', modification: new Attribute({ type: 'cn', vals: ['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() }) }) tap.test('modify change plain object success', function (t) { const change = new Change({ type: 'Replace', modification: { cn: 'test' } }) t.context.client.modify('cn=modify, ' + SUFFIX, change, function (err, res) { t.error(err) t.ok(res) t.equal(res.status, 0) t.end() }) }) // https://github.com/ldapjs/node-ldapjs/pull/435 tap.test('can delete attributes', function (t) { const change = new Change({ type: 'Delete', modification: { cn: null } }) 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 array success', function (t) { const changes = [ new Change({ operation: 'Replace', modification: new Attribute({ type: 'cn', vals: ['test'] }) }), new Change({ operation: 'Delete', modification: new Attribute({ type: 'sn' }) }) ] t.context.client.modify('cn=modify, ' + SUFFIX, changes, function (err, res) { t.error(err) t.ok(res) t.equal(res.status, 0) t.end() }) }) tap.test('modify change plain object success (GH-31)', function (t) { const change = { type: 'replace', modification: { cn: 'test', sn: 'bar' } } t.context.client.modify('cn=modify, ' + SUFFIX, change, function (err, res) { t.error(err) t.ok(res) t.equal(res.status, 0) t.end() }) }) tap.test('modify DN new RDN only', function (t) { t.context.client.modifyDN('cn=old, ' + SUFFIX, 'cn=new', function (err, res) { t.error(err) t.ok(res) t.equal(res.status, 0) t.end() }) }) tap.test('modify DN new superior', function (t) { t.context.client.modifyDN('cn=old, ' + SUFFIX, 'cn=new, dc=foo', function (err, res) { t.error(err) t.ok(res) t.equal(res.status, 0) t.end() }) }) tap.test('modify DN excessive length (GH-480)', function (t) { t.context.client.modifyDN('cn=issue-480', 'cn=a292979f2c86d513d48bbb9786b564b3c5228146e5ba46f404724e322544a7304a2b1049168803a5485e2d57a544c6a0d860af91330acb77e5907a9e601ad1227e80e0dc50abe963b47a004f2c90f570450d0e920d15436fdc771e3bdac0487a9735473ed3a79361d1778d7e53a7fb0e5f01f97a75ef05837d1d5496fc86968ff47fcb64', function (err, res) { t.error(err) t.ok(res) t.equal(res.status, 0) t.end() }) }) tap.test('modify DN excessive superior length', function (t) { const { BerReader, BerWriter } = require('asn1') const ModifyDNRequest = require('../lib/messages/moddn_request') const ber = new BerWriter() const entry = 'cn=Test User,ou=A Long OU ,ou=Another Long OU ,ou=Another Long OU ,dc=acompany,DC=io' const newSuperior = 'ou=A New Long OU , ou=Another New Long OU , ou=An OU , dc=acompany, dc=io' const newRdn = entry.replace(/(.*?),.*/, '$1') const deleteOldRdn = true const req = new ModifyDNRequest({ entry: entry, deleteOldRdn: deleteOldRdn, controls: [] }) req.newRdn = newRdn req.newSuperior = newSuperior req._toBer(ber) const reader = new BerReader(ber.buffer) t.equal(reader.readString(), entry) t.equal(reader.readString(), newRdn) t.equal(reader.readBoolean(), deleteOldRdn) t.equal(reader.readByte(), 0x80) reader.readLength() t.equal(reader._len, newSuperior.length) reader._buf[--reader._offset] = 0x4 t.equal(reader.readString(), newSuperior) t.end() }) tap.test('search basic', function (t) { t.context.client.search('cn=test, ' + SUFFIX, '(objectclass=*)', function (err, res) { t.error(err) t.ok(res) let gotEntry = 0 res.on('searchEntry', function (entry) { t.ok(entry) t.ok(entry instanceof ldap.SearchEntry) t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX) t.ok(entry.attributes) t.ok(entry.attributes.length) t.equal(entry.attributes[0].type, 'cn') t.equal(entry.attributes[1].type, 'SN') t.ok(entry.object) gotEntry++ }) res.on('error', function (err) { t.fail(err) }) res.on('end', function (res) { t.ok(res) t.ok(res instanceof ldap.SearchResponse) t.equal(res.status, 0) t.equal(gotEntry, 2) t.end() }) }) }) tap.test('GH-602 search basic with delayed event listener binding', function (t) { t.context.client.search('cn=test, ' + SUFFIX, '(objectclass=*)', function (err, res) { t.error(err) setTimeout(() => { let gotEntry = 0 res.on('searchEntry', function () { gotEntry++ }) res.on('error', function (err) { t.fail(err) }) res.on('end', function () { t.equal(gotEntry, 2) t.end() }) }, 100) }) }) tap.test('search sizeLimit', function (t) { t.test('over limit', function (t2) { t.context.client.search('cn=sizelimit', {}, function (err, res) { t2.error(err) res.on('error', function (error) { t2.equal(error.name, 'SizeLimitExceededError') t2.end() }) }) }) t.test('under limit', function (t2) { const limit = 100 t.context.client.search('cn=sizelimit', { sizeLimit: limit }, function (err, res) { t2.error(err) let count = 0 res.on('searchEntry', function () { count++ }) res.on('end', function () { t2.pass() t2.equal(count, limit) t2.end() }) res.on('error', t2.error.bind(t)) }) }) t.end() }) tap.test('search paged', { timeout: 10000 }, function (t) { t.test('paged - no pauses', function (t2) { let countEntries = 0 let countPages = 0 let currentSearchRequest = null t.context.client.search('cn=paged', { paged: { pageSize: 100 } }, function (err, res) { t2.error(err) res.on('searchEntry', entryListener) res.on('searchRequest', (searchRequest) => { t2.ok(searchRequest instanceof ldap.SearchRequest) if (currentSearchRequest === null) { t2.equal(countPages, 0) } currentSearchRequest = searchRequest }) res.on('page', pageListener) res.on('error', (err) => t2.error(err)) res.on('end', function (result) { t2.equal(countEntries, 1000) t2.equal(countPages, 10) t2.equal(result.messageID, currentSearchRequest.messageID) t2.end() }) t2.teardown(() => { res.removeListener('searchEntry', entryListener) res.removeListener('page', pageListener) }) function entryListener () { countEntries += 1 } function pageListener (result) { countPages += 1 if (countPages < 10) { t2.equal(result.messageID, currentSearchRequest.messageID) } } }) }) t.test('paged - pauses', function (t2) { let countPages = 0 t.context.client.search('cn=paged', { paged: { pageSize: 100, pagePause: true } }, function (err, res) { t2.error(err) res.on('page', pageListener) res.on('error', (err) => t2.error(err)) res.on('end', function () { t2.equal(countPages, 9) t2.end() }) function pageListener (result, cb) { countPages++ // cancel after 9 to verify callback usage if (countPages === 9) { // another page should never be encountered res.removeListener('page', pageListener) .on('page', t2.fail.bind(null, 'unexpected page')) return cb(new Error()) } return cb() } }) }) t.test('paged - no support (err handled)', function (t2) { t.context.client.search(SUFFIX, { paged: { pageSize: 100 } }, function (err, res) { t2.error(err) res.on('pageError', t2.ok.bind(t2)) res.on('end', function () { t2.pass() t2.end() }) }) }) t.test('paged - no support (err not handled)', function (t2) { t.context.client.search(SUFFIX, { paged: { pageSize: 100 } }, function (err, res) { t2.error(err) res.on('end', t2.fail.bind(t2)) res.on('error', function (error) { t2.ok(error) t2.end() }) }) }) t.test('paged - redundant control', function (t2) { try { t.context.client.search(SUFFIX, { paged: { pageSize: 100 } }, new ldap.PagedResultsControl(), function (err) { t.error(err) t2.fail() }) } catch (e) { t2.ok(e) t2.end() } }) t.test('paged - handle later error', function (t2) { let countEntries = 0 let countPages = 0 t.context.client.search('cn=pagederr', { paged: { pageSize: 1 } }, function (err, res) { t2.error(err) res.on('searchEntry', function () { t2.ok(++countEntries) }) res.on('page', function () { t2.ok(++countPages) }) res.on('error', function (error) { t2.ok(error) t2.equal(countEntries, 1) t2.equal(countPages, 1) t2.end() }) res.on('end', function () { t2.fail('should not be reached') }) }) }) tap.test('paged - search with delayed event listener binding', function (t) { t.context.client.search('cn=paged', { filter: '(objectclass=*)', paged: true }, function (err, res) { t.error(err) setTimeout(() => { let gotEntry = 0 res.on('searchEntry', function () { gotEntry++ }) res.on('error', function (err) { t.fail(err) }) res.on('end', function () { t.equal(gotEntry, 1000) t.end() }) }, 100) }) }) t.end() }) tap.test('search - sssvlv', { timeout: 10000 }, function (t) { t.test('ssv - asc', function (t2) { let preventry = null const sssrcontrol = new ldap.ServerSideSortingRequestControl( { value: { attributeType: 'o', orderingRule: 'caseIgnoreOrderingMatch', reverseOrder: false } } ) t.context.client.search('cn=sssvlv', {}, sssrcontrol, function (err, res) { t2.error(err) res.on('searchEntry', function (entry) { t2.ok(entry) t2.ok(entry instanceof ldap.SearchEntry) t2.ok(entry.attributes) t2.ok(entry.attributes.length) if (preventry != null) { t2.ok(entry.attributes[0]._vals[0] >= preventry.attributes[0]._vals[0]) } preventry = entry }) res.on('error', (err) => t2.error(err)) res.on('end', function () { t2.end() }) }) }) t.test('ssv - desc', function (t2) { let preventry = null const sssrcontrol = new ldap.ServerSideSortingRequestControl( { value: { attributeType: 'o', orderingRule: 'caseIgnoreOrderingMatch', reverseOrder: true } } ) t.context.client.search('cn=sssvlv', {}, sssrcontrol, function (err, res) { t2.error(err) res.on('searchEntry', function (entry) { t2.ok(entry) t2.ok(entry instanceof ldap.SearchEntry) t2.ok(entry.attributes) t2.ok(entry.attributes.length) if (preventry != null) { t2.ok(entry.attributes[0]._vals[0] <= preventry.attributes[0]._vals[0]) } preventry = entry }) res.on('error', (err) => t2.error(err)) res.on('end', function () { t2.end() }) }) }) t.test('vlv - first page', function (t2) { const sssrcontrol = new ldap.ServerSideSortingRequestControl( { value: { attributeType: 'o', orderingRule: 'caseIgnoreOrderingMatch', reverseOrder: false } } ) const vlvrcontrol = new ldap.VirtualListViewRequestControl( { value: { beforeCount: 0, afterCount: 9, targetOffset: 1, contentCount: 0 } } ) let count = 0 let preventry = null t.context.client.search('cn=sssvlv', {}, [sssrcontrol, vlvrcontrol], function (err, res) { t2.error(err) res.on('searchEntry', function (entry) { t2.ok(entry) t2.ok(entry instanceof ldap.SearchEntry) t2.ok(entry.attributes) t2.ok(entry.attributes.length) if (preventry != null) { t2.ok(entry.attributes[0]._vals[0] >= preventry.attributes[0]._vals[0]) } preventry = entry count++ }) res.on('error', (err) => t2.error(err)) res.on('end', function () { t2.equal(count, 10) t2.end() }) }) }) t.test('vlv - last page', function (t2) { const sssrcontrol = new ldap.ServerSideSortingRequestControl( { value: { attributeType: 'o', orderingRule: 'caseIgnoreOrderingMatch', reverseOrder: false } } ) const vlvrcontrol = new ldap.VirtualListViewRequestControl( { value: { beforeCount: 0, afterCount: 9, targetOffset: 91, contentCount: 0 } } ) let count = 0 let preventry = null t.context.client.search('cn=sssvlv', {}, [sssrcontrol, vlvrcontrol], function (err, res) { t2.error(err) res.on('searchEntry', function (entry) { t2.ok(entry) t2.ok(entry instanceof ldap.SearchEntry) t2.ok(entry.attributes) t2.ok(entry.attributes.length) if (preventry != null) { t2.ok(entry.attributes[0]._vals[0] >= preventry.attributes[0]._vals[0]) } preventry = entry count++ }) res.on('error', (err) => t2.error(err)) res.on('end', function () { t2.equal(count, 10) t2.end() }) }) }) t.end() }) tap.test('search referral', function (t) { t.context.client.search('cn=ref, ' + SUFFIX, '(objectclass=*)', function (err, res) { t.error(err) t.ok(res) let gotEntry = 0 let gotReferral = false res.on('searchEntry', function () { gotEntry++ }) res.on('searchReference', function (referral) { gotReferral = true t.ok(referral) t.ok(referral instanceof ldap.SearchReference) t.ok(referral.uris) t.ok(referral.uris.length) }) res.on('error', function (err) { t.fail(err) }) res.on('end', function (res) { t.ok(res) t.ok(res instanceof ldap.SearchResponse) t.equal(res.status, 0) t.equal(gotEntry, 0) t.ok(gotReferral) t.end() }) }) }) tap.test('search rootDSE', function (t) { t.context.client.search('', '(objectclass=*)', function (err, res) { t.error(err) t.ok(res) res.on('searchEntry', function (entry) { t.ok(entry) t.equal(entry.dn.toString(), '') t.ok(entry.attributes) t.ok(entry.object) }) res.on('error', function (err) { t.fail(err) }) res.on('end', function (res) { t.ok(res) t.ok(res instanceof ldap.SearchResponse) t.equal(res.status, 0) t.end() }) }) }) tap.test('search empty attribute', function (t) { t.context.client.search('dc=empty', '(objectclass=*)', function (err, res) { t.error(err) t.ok(res) let gotEntry = 0 res.on('searchEntry', function (entry) { const obj = entry.toObject() t.equal('dc=empty', obj.dn) t.ok(obj.member) t.equal(obj.member.length, 0) t.ok(obj['member;range=0-1']) t.ok(obj['member;range=0-1'].length) gotEntry++ }) res.on('error', function (err) { t.fail(err) }) res.on('end', function (res) { t.ok(res) t.ok(res instanceof ldap.SearchResponse) t.equal(res.status, 0) t.equal(gotEntry, 1) t.end() }) }) }) tap.test('GH-21 binary attributes', function (t) { t.context.client.search('cn=bin, ' + SUFFIX, '(objectclass=*)', function (err, res) { t.error(err) t.ok(res) let gotEntry = 0 const expect = Buffer.from('\u00bd + \u00bc = \u00be', 'utf8') const expect2 = Buffer.from([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA]) res.on('searchEntry', function (entry) { t.ok(entry) t.ok(entry instanceof ldap.SearchEntry) t.equal(entry.dn.toString(), 'cn=bin,' + SUFFIX) t.ok(entry.attributes) t.ok(entry.attributes.length) t.equal(entry.attributes[0].type, 'foo;binary') t.equal(entry.attributes[0].vals[0], expect.toString('base64')) t.equal(entry.attributes[0].buffers[0].toString('base64'), expect.toString('base64')) t.ok(entry.attributes[1].type, 'gb18030') t.equal(entry.attributes[1].buffers.length, 1) t.equal(expect2.length, entry.attributes[1].buffers[0].length) for (let i = 0; i < expect2.length; i++) { t.equal(expect2[i], entry.attributes[1].buffers[0][i]) } t.ok(entry.object) gotEntry++ }) res.on('error', function (err) { t.fail(err) }) res.on('end', function (res) { t.ok(res) t.ok(res instanceof ldap.SearchResponse) t.equal(res.status, 0) t.equal(gotEntry, 1) t.end() }) }) }) tap.test('GH-23 case insensitive attribute filtering', function (t) { const opts = { filter: '(objectclass=*)', attributes: ['Cn'] } t.context.client.search('cn=test, ' + SUFFIX, opts, function (err, res) { t.error(err) t.ok(res) let gotEntry = 0 res.on('searchEntry', function (entry) { t.ok(entry) t.ok(entry instanceof ldap.SearchEntry) t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX) t.ok(entry.attributes) t.ok(entry.attributes.length) t.equal(entry.attributes[0].type, 'cn') t.ok(entry.object) gotEntry++ }) res.on('error', function (err) { t.fail(err) }) res.on('end', function (res) { t.ok(res) t.ok(res instanceof ldap.SearchResponse) t.equal(res.status, 0) t.equal(gotEntry, 2) t.end() }) }) }) tap.test('GH-24 attribute selection of *', function (t) { const opts = { filter: '(objectclass=*)', attributes: ['*'] } t.context.client.search('cn=test, ' + SUFFIX, opts, function (err, res) { t.error(err) t.ok(res) let gotEntry = 0 res.on('searchEntry', function (entry) { t.ok(entry) t.ok(entry instanceof ldap.SearchEntry) t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX) t.ok(entry.attributes) t.ok(entry.attributes.length) t.equal(entry.attributes[0].type, 'cn') t.equal(entry.attributes[1].type, 'SN') t.ok(entry.object) gotEntry++ }) res.on('error', function (err) { t.fail(err) }) res.on('end', function (res) { t.ok(res) t.ok(res instanceof ldap.SearchResponse) t.equal(res.status, 0) t.equal(gotEntry, 2) t.end() }) }) }) tap.test('idle timeout', function (t) { t.context.client.idleTimeout = 250 function premature () { t.error(true) } t.context.client.on('idle', premature) t.context.client.search('dc=slow', 'objectclass=*', function (err, res) { t.error(err) res.on('searchEntry', function (res) { t.ok(res) }) res.on('error', function (err) { t.error(err) }) res.on('end', function () { const late = setTimeout(function () { t.fail('too late') }, 500) // It's ok to go idle now t.context.client.removeListener('idle', premature) t.context.client.on('idle', function () { clearTimeout(late) t.context.client.removeAllListeners('idle') t.context.client.idleTimeout = 0 t.end() }) }) }) }) tap.test('setup action', function (t) { const setupClient = ldap.createClient({ connectTimeout: parseInt(LDAP_CONNECT_TIMEOUT, 10), socketPath: t.context.socketPath }) setupClient.on('setup', function (clt, cb) { clt.bind(BIND_DN, BIND_PW, function (err) { t.error(err) cb(err) }) }) setupClient.search(SUFFIX, { scope: 'base' }, function (err, res) { t.error(err) t.ok(res) res.on('end', function () { setupClient.destroy() t.end() }) }) }) tap.test('setup reconnect', function (t) { const rClient = ldap.createClient({ connectTimeout: parseInt(LDAP_CONNECT_TIMEOUT, 10), socketPath: t.context.socketPath, reconnect: true }) rClient.on('setup', function (clt, cb) { clt.bind(BIND_DN, BIND_PW, function (err) { t.error(err) cb(err) }) }) function doSearch (_, cb) { rClient.search(SUFFIX, { scope: 'base' }, function (err, res) { t.error(err) res.on('end', function () { cb() }) }) } vasync.pipeline({ funcs: [ doSearch, function cleanDisconnect (_, cb) { t.ok(rClient.connected) rClient.once('close', function (err) { t.error(err) t.equal(rClient.connected, false) cb() }) rClient.unbind() }, doSearch, function simulateError (_, cb) { const msg = 'fake socket error' rClient.once('error', function (err) { t.equal(err.message, msg) t.ok(err) }) rClient.once('close', function () { // can't test had_err because the socket error is being faked cb() }) rClient._socket.emit('error', new Error(msg)) }, doSearch ] }, function (err) { t.error(err) rClient.destroy() t.end() }) }) tap.test('setup abort', function (t) { const setupClient = ldap.createClient({ connectTimeout: parseInt(LDAP_CONNECT_TIMEOUT, 10), socketPath: t.context.socketPath, reconnect: true }) const message = "It's a trap!" setupClient.on('setup', function (clt, cb) { // simulate failure t.ok(clt) cb(new Error(message)) }) setupClient.on('setupError', function (err) { t.ok(true) t.equal(err.message, message) setupClient.destroy() t.end() }) }) tap.test('abort reconnect', function (t) { const abortClient = ldap.createClient({ connectTimeout: parseInt(LDAP_CONNECT_TIMEOUT, 10), socketPath: 'an invalid path', reconnect: true }) let retryCount = 0 abortClient.on('connectError', function () { ++retryCount }) abortClient.once('connectError', function () { t.ok(true) abortClient.once('destroy', function () { t.ok(retryCount < 3) t.end() }) abortClient.destroy() }) }) tap.test('reconnect max retries', function (t) { const RETRIES = 5 const rClient = ldap.createClient({ connectTimeout: 100, socketPath: 'an invalid path', reconnect: { failAfter: RETRIES, // Keep the test duration low initialDelay: 10, maxDelay: 100 } }) let count = 0 rClient.on('connectError', function () { count++ }) rClient.on('error', function (err) { t.ok(err) t.equal(count, RETRIES) rClient.destroy() t.end() }) }) tap.test('reconnect on server close', function (t) { const clt = ldap.createClient({ socketPath: t.context.socketPath, reconnect: true }) clt.on('setup', function (sclt, cb) { sclt.bind(BIND_DN, BIND_PW, function (err) { t.error(err) cb(err) }) }) clt.once('connect', function () { t.ok(clt._socket) clt.once('connect', function () { t.ok(true, 'successful reconnect') clt.destroy() t.end() }) // Simulate server-side close clt._socket.destroy() }) }) tap.test('no auto-reconnect on unbind', function (t) { const clt = ldap.createClient({ socketPath: t.context.socketPath, reconnect: true }) clt.on('setup', function (sclt, cb) { sclt.bind(BIND_DN, BIND_PW, function (err) { t.error(err) cb(err) }) }) clt.once('connect', function () { clt.once('connect', function () { t.error(new Error('client should not reconnect')) }) clt.once('close', function () { t.ok(true, 'initial close') setImmediate(function () { t.ok(!clt.connected, 'should not be connected') t.ok(!clt.connecting, 'should not be connecting') clt.destroy() t.end() }) }) clt.unbind() }) }) tap.test('abandon (GH-27)', function (t) { // FIXME: test abandoning a real request t.context.client.abandon(401876543, function (err) { t.error(err) t.end() }) }) tap.test('search timeout (GH-51)', function (t) { t.context.client.timeout = 250 t.context.client.search('dc=timeout', 'objectclass=*', function (err, res) { t.error(err) res.on('error', function () { t.end() }) }) }) tap.test('resultError handling', function (t) { const client = t.context.client vasync.pipeline({ funcs: [errSearch, cleanSearch] }, function (err) { t.error(err) client.removeListener('resultError', error1) client.removeListener('resultError', error2) t.end() }) function errSearch (_, cb) { client.once('resultError', error1) client.search('cn=busy', {}, function (err, res) { t.error(err) res.once('error', function (error) { t.equal(error.name, 'BusyError') cb() }) }) } function cleanSearch (_, cb) { client.on('resultError', error2) client.search(SUFFIX, {}, function (err, res) { t.error(err) res.once('end', function () { t.pass() cb() }) }) } function error1 (error) { t.equal(error.name, 'BusyError') } function error2 () { t.fail('should not get error') } }) tap.test('connection refused', function (t) { getPort().then(function (unusedPortNumber) { const client = ldap.createClient({ url: `ldap://0.0.0.0:${unusedPortNumber}` }) client.on('connectRefused', () => {}) client.bind('cn=root', 'secret', function (err, res) { t.ok(err) t.type(err, Error) t.equal(err.code, 'ECONNREFUSED') t.notOk(res) t.end() }) }) }) tap.test('connection timeout', function (t) { getPort().then(function (unusedPortNumber) { const client = ldap.createClient({ url: `ldap://example.org:${unusedPortNumber}`, connectTimeout: 1, timeout: 1 }) client.on('connectTimeout', () => {}) let done = false setTimeout(function () { if (!done) { throw new Error('LDAPJS waited for the server for too long') } }, 2000) client.bind('cn=root', 'secret', function (err, res) { t.ok(err) t.type(err, Error) t.equal(err.message, 'connection timeout') done = true t.notOk(res) t.end() }) }) }) tap.only('emitError', function (t) { t.test('connectTimeout', function (t) { getPort().then(function (unusedPortNumber) { const client = ldap.createClient({ url: `ldap://example.org:${unusedPortNumber}`, connectTimeout: 1, timeout: 1 }) const timeout = setTimeout(function () { throw new Error('LDAPJS waited for the server for too long') }, 2000) client.on('error', (err) => { t.fail(err) }) client.on('connectTimeout', (err) => { t.ok(err) t.type(err, Error) t.equal(err.message, 'connection timeout') clearTimeout(timeout) t.end() }) client.bind('cn=root', 'secret', () => {}) }) }) t.test('connectTimeout to error', function (t) { getPort().then(function (unusedPortNumber) { const client = ldap.createClient({ url: `ldap://example.org:${unusedPortNumber}`, connectTimeout: 1, timeout: 1 }) const timeout = setTimeout(function () { throw new Error('LDAPJS waited for the server for too long') }, 2000) client.on('error', (err) => { t.ok(err) t.type(err, Error) t.equal(err.message, 'connectTimeout: connection timeout') clearTimeout(timeout) t.end() }) client.bind('cn=root', 'secret', () => {}) }) }) t.test('connectRefused', function (t) { getPort().then(function (unusedPortNumber) { const client = ldap.createClient({ url: `ldap://0.0.0.0:${unusedPortNumber}` }) client.on('error', (err) => { t.fail(err) }) client.on('connectRefused', (err) => { t.ok(err) t.type(err, Error) t.equal(err.message, `connect ECONNREFUSED 0.0.0.0:${unusedPortNumber}`) t.equal(err.code, 'ECONNREFUSED') t.end() }) client.bind('cn=root', 'secret', () => {}) }) }) t.test('connectRefused to error', function (t) { getPort().then(function (unusedPortNumber) { const client = ldap.createClient({ url: `ldap://0.0.0.0:${unusedPortNumber}` }) client.on('error', (err) => { t.ok(err) t.type(err, Error) t.equal(err.message, `connectRefused: connect ECONNREFUSED 0.0.0.0:${unusedPortNumber}`) t.equal(err.code, 'ECONNREFUSED') t.end() }) client.bind('cn=root', 'secret', () => {}) }) }) t.end() }) tap.test('socket destroy', function (t) { const clt = ldap.createClient({ socketPath: t.context.socketPath, bindDN: BIND_DN, bindCredentials: BIND_PW }) clt.once('connect', function () { t.ok(clt) clt._socket.once('close', function () { t.ok(!clt.connected) t.end() }) clt.destroy() }) clt.once('destroy', function () { t.ok(clt.destroyed) }) }) node-ldapjs-2.3.3/test/controls/000077500000000000000000000000001424777304200165535ustar00rootroot00000000000000node-ldapjs-2.3.3/test/controls/control.test.js000066400000000000000000000022001424777304200215410ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { Control, getControl } = require('../../lib') test('new no args', function (t) { t.ok(new Control()) t.end() }) test('new with args', function (t) { const c = new Control({ type: '2.16.840.1.113730.3.4.2', criticality: true }) t.ok(c) t.equal(c.type, '2.16.840.1.113730.3.4.2') t.ok(c.criticality) t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.startSequence() ber.writeString('2.16.840.1.113730.3.4.2') ber.writeBoolean(true) ber.writeString('foo') ber.endSequence() const c = getControl(new BerReader(ber.buffer)) t.ok(c) t.equal(c.type, '2.16.840.1.113730.3.4.2') t.ok(c.criticality) t.equal(c.value.toString('utf8'), 'foo') t.end() }) test('parse no value', function (t) { const ber = new BerWriter() ber.startSequence() ber.writeString('2.16.840.1.113730.3.4.2') ber.endSequence() const c = getControl(new BerReader(ber.buffer)) t.ok(c) t.equal(c.type, '2.16.840.1.113730.3.4.2') t.equal(c.criticality, false) t.notOk(c.value, null) t.end() }) node-ldapjs-2.3.3/test/controls/entry_change_notification_control.test.js000066400000000000000000000031651424777304200270500ustar00rootroot00000000000000'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() }) node-ldapjs-2.3.3/test/controls/paged_results_control.test.js000066400000000000000000000026401424777304200244720ustar00rootroot00000000000000'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() }) node-ldapjs-2.3.3/test/controls/persistent_search_control.test.js000066400000000000000000000046361424777304200253650ustar00rootroot00000000000000// 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() }) node-ldapjs-2.3.3/test/controls/server_side_sorting_control_request.test.js000066400000000000000000000042241424777304200274600ustar00rootroot00000000000000'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() }) node-ldapjs-2.3.3/test/controls/server_side_sorting_control_response.test.js000066400000000000000000000045361424777304200276340ustar00rootroot00000000000000'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() }) node-ldapjs-2.3.3/test/controls/virtual_list_view_request_control.test.js000066400000000000000000000040701424777304200271530ustar00rootroot00000000000000'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() }) node-ldapjs-2.3.3/test/controls/virtual_list_view_response_control.test.js000066400000000000000000000031321424777304200273170ustar00rootroot00000000000000'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() }) node-ldapjs-2.3.3/test/corked_emitter.test.js000066400000000000000000000062471424777304200212350ustar00rootroot00000000000000'use strict' const { test } = require('tap') const CorkedEmitter = require('../lib/corked_emitter') function gatherEventSequence (expectedNumber) { const gatheredEvents = [] let callback const finished = new Promise(function (resolve) { callback = function (...args) { gatheredEvents.push(...args) if (gatheredEvents.length >= expectedNumber) { // Prevent result mutation after our promise is resolved: resolve(gatheredEvents.slice()) } } }) return { finished, callback } } test('normal emit flow', function (t) { const emitter = new CorkedEmitter() const expectedSequence = [ ['searchEntry', { data: 'a' }], ['searchEntry', { data: 'b' }], ['end'] ] const gatherer = gatherEventSequence(3) emitter.on('searchEntry', function (...args) { gatherer.callback(['searchEntry', ...args]) }) emitter.on('end', function (...args) { gatherer.callback(['end', ...args]) }) emitter.emit('searchEntry', { data: 'a' }) emitter.emit('searchEntry', { data: 'b' }) emitter.emit('end') gatherer.finished.then(function (gatheredEvents) { expectedSequence.forEach(function (expectedEvent, i) { t.equal(JSON.stringify(expectedEvent), JSON.stringify(gatheredEvents[i])) }) t.end() }) }) test('reversed listener registration', function (t) { const emitter = new CorkedEmitter() const expectedSequence = [ ['searchEntry', { data: 'a' }], ['searchEntry', { data: 'b' }], ['end'] ] const gatherer = gatherEventSequence(3) // This time, we swap the event listener registrations. // The order of emits should remain unchanged. emitter.on('end', function (...args) { gatherer.callback(['end', ...args]) }) emitter.on('searchEntry', function (...args) { gatherer.callback(['searchEntry', ...args]) }) emitter.emit('searchEntry', { data: 'a' }) emitter.emit('searchEntry', { data: 'b' }) emitter.emit('end') gatherer.finished.then(function (gatheredEvents) { expectedSequence.forEach(function (expectedEvent, i) { t.equal(JSON.stringify(expectedEvent), JSON.stringify(gatheredEvents[i])) }) t.end() }) }) test('delayed listener registration', function (t) { const emitter = new CorkedEmitter() const expectedSequence = [ ['searchEntry', { data: 'a' }], ['searchEntry', { data: 'b' }], ['end'] ] const gatherer = gatherEventSequence(3) emitter.emit('searchEntry', { data: 'a' }) emitter.emit('searchEntry', { data: 'b' }) emitter.emit('end') // The listeners only appear after a brief delay - this simulates // the situation described in https://github.com/ldapjs/node-ldapjs/issues/602 // and in https://github.com/ifroz/node-ldapjs/commit/5239f6c68827f2c25b4589089c199d15bb882412 setTimeout(function () { emitter.on('end', function (...args) { gatherer.callback(['end', ...args]) }) emitter.on('searchEntry', function (...args) { gatherer.callback(['searchEntry', ...args]) }) }, 50) gatherer.finished.then(function (gatheredEvents) { expectedSequence.forEach(function (expectedEvent, i) { t.equal(JSON.stringify(expectedEvent), JSON.stringify(gatheredEvents[i])) }) t.end() }) }) node-ldapjs-2.3.3/test/dn.test.js000066400000000000000000000151401424777304200166260ustar00rootroot00000000000000'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() }) node-ldapjs-2.3.3/test/errors.test.js000066400000000000000000000022551424777304200175440ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { LDAPError, ConnectionError, AbandonedError, TimeoutError, ConstraintViolationError, LDAP_OTHER } = require('../lib') test('basic error', function (t) { const msg = 'mymsg' const err = new LDAPError(msg, null, null) t.ok(err) t.equal(err.name, 'LDAPError') t.equal(err.code, LDAP_OTHER) t.equal(err.dn, '') t.equal(err.message, msg) t.end() }) test('exports ConstraintViolationError', function (t) { const msg = 'mymsg' const err = new ConstraintViolationError(msg, null, null) t.ok(err) t.equal(err.name, 'ConstraintViolationError') t.equal(err.code, 19) t.equal(err.dn, '') t.equal(err.message, msg) t.end() }) test('"custom" errors', function (t) { const errors = [ { name: 'ConnectionError', Func: ConnectionError }, { name: 'AbandonedError', Func: AbandonedError }, { name: 'TimeoutError', Func: TimeoutError } ] errors.forEach(function (entry) { const msg = entry.name + 'msg' const err = new entry.Func(msg) t.ok(err) t.equal(err.name, entry.name) t.equal(err.code, LDAP_OTHER) t.equal(err.dn, '') t.equal(err.message, msg) }) t.end() }) node-ldapjs-2.3.3/test/filters/000077500000000000000000000000001424777304200163605ustar00rootroot00000000000000node-ldapjs-2.3.3/test/filters/and.test.js000066400000000000000000000020731424777304200204400ustar00rootroot00000000000000'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() }) node-ldapjs-2.3.3/test/filters/approx.test.js000066400000000000000000000033641424777304200212130ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { filters: { ApproximateFilter } } = require('../../lib') test('Construct no args', function (t) { const f = new ApproximateFilter() t.ok(f) t.ok(!f.attribute) t.ok(!f.value) t.end() }) test('Construct args', function (t) { const f = new ApproximateFilter({ attribute: 'foo', value: 'bar' }) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.value, 'bar') t.equal(f.toString(), '(foo~=bar)') t.end() }) test('GH-109 = escape value only in toString()', function (t) { const f = new ApproximateFilter({ attribute: 'foo', value: 'ba(r)' }) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.value, 'ba(r)') t.equal(f.toString(), '(foo~=ba\\28r\\29)') t.end() }) test('parse ok', function (t) { const writer = new BerWriter() writer.writeString('foo') writer.writeString('bar') const f = new ApproximateFilter() t.ok(f) t.ok(f.parse(new BerReader(writer.buffer))) t.end() }) test('parse bad', function (t) { const writer = new BerWriter() writer.writeString('foo') writer.writeInt(20) const f = new ApproximateFilter() t.ok(f) try { f.parse(new BerReader(writer.buffer)) t.fail('Should have thrown InvalidAsn1Error') } catch (e) { t.equal(e.name, 'InvalidAsn1Error') } t.end() }) test('GH-109 = to ber uses plain values', function (t) { let f = new ApproximateFilter({ attribute: 'foo', value: 'ba(r)' }) t.ok(f) const writer = new BerWriter() f.toBer(writer) f = new ApproximateFilter() t.ok(f) const reader = new BerReader(writer.buffer) reader.readSequence() t.ok(f.parse(reader)) t.equal(f.attribute, 'foo') t.equal(f.value, 'ba(r)') t.end() }) node-ldapjs-2.3.3/test/filters/eq.test.js000066400000000000000000000067621424777304200203140ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { filters: { EqualityFilter } } = require('../../lib') test('Construct no args', function (t) { const f = new EqualityFilter() t.ok(f) t.ok(!f.attribute) t.ok(!f.value) t.end() }) test('Construct args', function (t) { const f = new EqualityFilter({ attribute: 'foo', value: 'bar' }) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.value, 'bar') t.equal(f.toString(), '(foo=bar)') t.end() }) test('GH-109 = escape value only in toString()', function (t) { const f = new EqualityFilter({ attribute: 'foo', value: 'ba(r)' }) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.value, 'ba(r)') t.equal(f.toString(), '(foo=ba\\28r\\29)') t.end() }) test('match true', function (t) { const f = new EqualityFilter({ attribute: 'foo', value: 'bar' }) t.ok(f) t.ok(f.matches({ foo: 'bar' })) t.end() }) test('match multiple', function (t) { const f = new EqualityFilter({ attribute: 'foo', value: 'bar' }) t.ok(f) t.ok(f.matches({ foo: ['plop', 'bar'] })) t.end() }) test('match false', function (t) { const f = new EqualityFilter({ attribute: 'foo', value: 'bar' }) t.ok(f) t.ok(!f.matches({ foo: 'baz' })) t.end() }) test('parse ok', function (t) { const writer = new BerWriter() writer.writeString('foo') writer.writeString('bar') const f = new EqualityFilter() t.ok(f) t.ok(f.parse(new BerReader(writer.buffer))) t.ok(f.matches({ foo: 'bar' })) t.equal(f.attribute, 'foo') t.equal(f.value, 'bar') t.end() }) test('escape EqualityFilter inputs', function (t) { const f = new EqualityFilter({ attribute: '(|(foo', value: 'bar))(' }) t.equal(f.attribute, '(|(foo') t.equal(f.value, 'bar))(') t.equal(f.toString(), '(\\28|\\28foo=bar\\29\\29\\28)') t.end() }) test('parse bad', function (t) { const writer = new BerWriter() writer.writeString('foo') writer.writeInt(20) const f = new EqualityFilter() t.ok(f) try { f.parse(new BerReader(writer.buffer)) t.fail('Should have thrown InvalidAsn1Error') } catch (e) { t.equal(e.name, 'InvalidAsn1Error') } t.end() }) test('GH-109 = to ber uses plain values', function (t) { let f = new EqualityFilter({ attribute: 'foo', value: 'ba(r)' }) t.ok(f) const writer = new BerWriter() f.toBer(writer) f = new EqualityFilter() t.ok(f) const reader = new BerReader(writer.buffer) reader.readSequence() t.ok(f.parse(reader)) t.equal(f.attribute, 'foo') t.equal(f.value, 'ba(r)') t.end() }) test('handle values passed via buffer', function (t) { const b = Buffer.from([32, 64, 128, 254]) const f = new EqualityFilter({ attribute: 'foo', value: b }) t.ok(f) const writer = new BerWriter() f.toBer(writer) const reader = new BerReader(writer.buffer) reader.readSequence() const f2 = new EqualityFilter() t.ok(f2.parse(reader)) t.equal(f2.value, b.toString()) t.equal(f2.raw.length, b.length) for (let i = 0; i < b.length; i++) { t.equal(f2.raw[i], b[i]) } t.end() }) test('GH-277 objectClass should be case-insensitive', function (t) { const f = new EqualityFilter({ attribute: 'objectClass', value: 'CaseInsensitiveObj' }) t.ok(f) t.ok(f.matches({ objectClass: 'CaseInsensitiveObj' })) t.ok(f.matches({ OBJECTCLASS: 'CASEINSENSITIVEOBJ' })) t.ok(f.matches({ objectclass: 'caseinsensitiveobj' })) t.ok(!f.matches({ objectclass: 'matchless' })) t.end() }) node-ldapjs-2.3.3/test/filters/ext.test.js000066400000000000000000000036251424777304200205020ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { filters: { parseString, ExtensibleFilter } } = require('../../lib') test('Construct no args', function (t) { const f = new ExtensibleFilter() t.ok(f) t.end() }) test('Construct args', function (t) { const f = new ExtensibleFilter({ matchType: 'foo', value: 'bar' }) t.ok(f) t.equal(f.matchType, 'foo') t.equal(f.value, 'bar') t.equal(f.toString(), '(foo:=bar)') t.end() }) test('parse RFC example 1', function (t) { const f = parseString('(cn:caseExactMatch:=Fred Flintstone)') t.ok(f) t.equal(f.matchType, 'cn') t.equal(f.matchingRule, 'caseExactMatch') t.equal(f.matchValue, 'Fred Flintstone') t.notOk(f.dnAttributes) t.end() }) test('parse RFC example 2', function (t) { const f = parseString('(cn:=Betty Rubble)') t.ok(f) t.equal(f.matchType, 'cn') t.equal(f.matchValue, 'Betty Rubble') t.notOk(f.dnAttributes) t.notOk(f.matchingRule) t.end() }) test('parse RFC example 3', function (t) { const f = parseString('(sn:dn:2.4.6.8.10:=Barney Rubble)') t.ok(f) t.equal(f.matchType, 'sn') t.equal(f.matchingRule, '2.4.6.8.10') t.equal(f.matchValue, 'Barney Rubble') t.ok(f.dnAttributes) t.end() }) test('parse RFC example 3', function (t) { const f = parseString('(o:dn:=Ace Industry)') t.ok(f) t.equal(f.matchType, 'o') t.notOk(f.matchingRule) t.equal(f.matchValue, 'Ace Industry') t.ok(f.dnAttributes) t.end() }) test('parse RFC example 4', function (t) { const f = parseString('(:1.2.3:=Wilma Flintstone)') t.ok(f) t.notOk(f.matchType) t.equal(f.matchingRule, '1.2.3') t.equal(f.matchValue, 'Wilma Flintstone') t.notOk(f.dnAttributes) t.end() }) test('parse RFC example 5', function (t) { const f = parseString('(:DN:2.4.6.8.10:=Dino)') t.ok(f) t.notOk(f.matchType) t.equal(f.matchingRule, '2.4.6.8.10') t.equal(f.matchValue, 'Dino') t.ok(f.dnAttributes) t.end() }) node-ldapjs-2.3.3/test/filters/ge.test.js000066400000000000000000000045551424777304200203000ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { filters: { GreaterThanEqualsFilter } } = require('../../lib') test('Construct no args', function (t) { const f = new GreaterThanEqualsFilter() t.ok(f) t.ok(!f.attribute) t.ok(!f.value) t.end() }) test('Construct args', function (t) { const f = new GreaterThanEqualsFilter({ attribute: 'foo', value: 'bar' }) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.value, 'bar') t.equal(f.toString(), '(foo>=bar)') t.end() }) test('GH-109 = escape value only in toString()', function (t) { const f = new GreaterThanEqualsFilter({ attribute: 'foo', value: 'ba(r)' }) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.value, 'ba(r)') t.equal(f.toString(), '(foo>=ba\\28r\\29)') t.end() }) test('match true', function (t) { const f = new GreaterThanEqualsFilter({ attribute: 'foo', value: 'bar' }) t.ok(f) t.ok(f.matches({ foo: 'baz' })) t.end() }) test('match multiple', function (t) { const f = new GreaterThanEqualsFilter({ attribute: 'foo', value: 'bar' }) t.ok(f) t.ok(f.matches({ foo: ['beuha', 'baz'] })) t.end() }) test('match false', function (t) { const f = new GreaterThanEqualsFilter({ attribute: 'foo', value: 'bar' }) t.ok(f) t.ok(!f.matches({ foo: 'abc' })) t.end() }) test('parse ok', function (t) { const writer = new BerWriter() writer.writeString('foo') writer.writeString('bar') const f = new GreaterThanEqualsFilter() t.ok(f) t.ok(f.parse(new BerReader(writer.buffer))) t.ok(f.matches({ foo: 'bar' })) t.end() }) test('parse bad', function (t) { const writer = new BerWriter() writer.writeString('foo') writer.writeInt(20) const f = new GreaterThanEqualsFilter() t.ok(f) try { f.parse(new BerReader(writer.buffer)) t.fail('Should have thrown InvalidAsn1Error') } catch (e) { t.equal(e.name, 'InvalidAsn1Error') } t.end() }) test('GH-109 = to ber uses plain values', function (t) { let f = new GreaterThanEqualsFilter({ attribute: 'foo', value: 'ba(r)' }) t.ok(f) const writer = new BerWriter() f.toBer(writer) f = new GreaterThanEqualsFilter() t.ok(f) const reader = new BerReader(writer.buffer) reader.readSequence() t.ok(f.parse(reader)) t.equal(f.attribute, 'foo') t.equal(f.value, 'ba(r)') t.end() }) node-ldapjs-2.3.3/test/filters/le.test.js000066400000000000000000000045141424777304200203000ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { filters: { LessThanEqualsFilter } } = require('../../lib') test('Construct no args', function (t) { const f = new LessThanEqualsFilter() t.ok(f) t.ok(!f.attribute) t.ok(!f.value) t.end() }) test('Construct args', function (t) { const f = new LessThanEqualsFilter({ attribute: 'foo', value: 'bar' }) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.value, 'bar') t.equal(f.toString(), '(foo<=bar)') t.end() }) test('GH-109 = escape value only in toString()', function (t) { const f = new LessThanEqualsFilter({ attribute: 'foo', value: 'ba(r)' }) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.value, 'ba(r)') t.equal(f.toString(), '(foo<=ba\\28r\\29)') t.end() }) test('match true', function (t) { const f = new LessThanEqualsFilter({ attribute: 'foo', value: 'bar' }) t.ok(f) t.ok(f.matches({ foo: 'abc' })) t.end() }) test('match multiple', function (t) { const f = new LessThanEqualsFilter({ attribute: 'foo', value: 'bar' }) t.ok(f) t.ok(f.matches({ foo: ['abc', 'beuha'] })) t.end() }) test('match false', function (t) { const f = new LessThanEqualsFilter({ attribute: 'foo', value: 'bar' }) t.ok(f) t.ok(!f.matches({ foo: 'baz' })) t.end() }) test('parse ok', function (t) { const writer = new BerWriter() writer.writeString('foo') writer.writeString('bar') const f = new LessThanEqualsFilter() t.ok(f) t.ok(f.parse(new BerReader(writer.buffer))) t.ok(f.matches({ foo: 'bar' })) t.end() }) test('parse bad', function (t) { const writer = new BerWriter() writer.writeString('foo') writer.writeInt(20) const f = new LessThanEqualsFilter() t.ok(f) try { f.parse(new BerReader(writer.buffer)) t.fail('Should have thrown InvalidAsn1Error') } catch (e) { t.equal(e.name, 'InvalidAsn1Error') } t.end() }) test('GH-109 = to ber uses plain values', function (t) { let f = new LessThanEqualsFilter({ attribute: 'foo', value: 'ba(r)' }) t.ok(f) const writer = new BerWriter() f.toBer(writer) f = new LessThanEqualsFilter() t.ok(f) const reader = new BerReader(writer.buffer) reader.readSequence() t.ok(f.parse(reader)) t.equal(f.attribute, 'foo') t.equal(f.value, 'ba(r)') t.end() }) node-ldapjs-2.3.3/test/filters/not.test.js000066400000000000000000000015011424777304200204710ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { filters: { EqualityFilter, NotFilter } } = require('../../lib') test('Construct no args', function (t) { t.ok(new NotFilter()) t.end() }) test('Construct args', function (t) { const f = new NotFilter({ filter: new EqualityFilter({ attribute: 'foo', value: 'bar' }) }) t.ok(f) t.equal(f.toString(), '(!(foo=bar))') t.end() }) test('match true', function (t) { const f = new NotFilter({ filter: new EqualityFilter({ attribute: 'foo', value: 'bar' }) }) t.ok(f) t.ok(f.matches({ foo: 'baz' })) t.end() }) test('match false', function (t) { const f = new NotFilter({ filter: new EqualityFilter({ attribute: 'foo', value: 'bar' }) }) t.ok(f) t.ok(!f.matches({ foo: 'bar' })) t.end() }) node-ldapjs-2.3.3/test/filters/or.test.js000066400000000000000000000020671424777304200203210ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { filters: { EqualityFilter, OrFilter } } = require('../../lib') test('Construct no args', function (t) { t.ok(new OrFilter()) t.end() }) test('Construct args', function (t) { const f = new OrFilter() 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 OrFilter() 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() }) test('match false', function (t) { const f = new OrFilter() f.addFilter(new EqualityFilter({ attribute: 'foo', value: 'bar' })) f.addFilter(new EqualityFilter({ attribute: 'zig', value: 'zag' })) t.ok(f) t.ok(!f.matches({ foo: 'baz', zig: 'zonk' })) t.end() }) node-ldapjs-2.3.3/test/filters/parse.test.js000066400000000000000000000062141424777304200210110ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { parseFilter: parse } = require('../../lib') test('GH-48 XML Strings in filter', function (t) { const str = '(&(CentralUIEnrollments=\\*)(objectClass=User))' const f = parse(str) t.ok(f) t.ok(f.filters) t.equal(f.filters.length, 2) f.filters.forEach(function (filter) { t.ok(filter.attribute) }) t.end() }) test('GH-50 = in filter', function (t) { const str = '(uniquemember=uuid=930896af-bf8c-48d4-885c-6573a94b1853, ' + 'ou=users, o=smartdc)' const f = parse(str) t.ok(f) t.equal(f.attribute, 'uniquemember') t.equal(f.value, 'uuid=930896af-bf8c-48d4-885c-6573a94b1853, ou=users, o=smartdc') t.end() }) test('convert to hex code', function (t) { const str = 'foo=bar\\(abcd\\e\\fg\\h\\69\\a' const f = parse(str) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.value, 'bar(abcdefghia') t.equal(f.toString(), '(foo=bar\\28abcdefghia)') t.end() }) test('( in filter', function (t) { const str = '(foo=bar\\()' const f = parse(str) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.value, 'bar(') t.equal(f.toString(), '(foo=bar\\28)') t.end() }) test(') in filter', function (t) { const str = '(foo=bar\\))' const f = parse(str) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.value, 'bar)') t.equal(f.toString(), '(foo=bar\\29)') t.end() }) test('\\ in filter', function (t) { const str = '(foo=bar\\\\)' const f = parse(str) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.value, 'bar\\') t.equal(f.toString(), '(foo=bar\\5c)') t.end() }) test('not escaped \\ at end of filter', function (t) { const str = 'foo=bar\\' const f = parse(str) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.value, 'bar\\') t.equal(f.toString(), '(foo=bar\\5c)') t.end() }) test('* in equality filter', function (t) { const str = '(foo=bar\\*)' const f = parse(str) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.value, 'bar*') t.equal(f.toString(), '(foo=bar\\2a)') t.end() }) test('* substr filter (prefix)', function (t) { const str = '(foo=bar*)' const f = parse(str) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.initial, 'bar') t.equal(f.toString(), '(foo=bar*)') t.end() }) test('GH-53 NotFilter', function (t) { const str = '(&(objectClass=person)(!(objectClass=shadowAccount)))' const f = parse(str) t.ok(f) t.equal(f.type, 'and') t.equal(f.filters.length, 2) t.equal(f.filters[0].type, 'equal') t.equal(f.filters[1].type, 'not') t.equal(f.filters[1].filter.type, 'equal') t.equal(f.filters[1].filter.attribute, 'objectClass') t.equal(f.filters[1].filter.value, 'shadowAccount') t.end() }) test('presence filter', function (t) { const f = parse('(foo=*)') t.ok(f) t.equal(f.type, 'present') t.equal(f.attribute, 'foo') t.equal(f.toString(), '(foo=*)') t.end() }) test('bogus filter', function (t) { t.throws(function () { parse('foo>1') }) t.end() }) test('bogus filter !=', function (t) { t.throws(function () { parse('foo!=1') }) t.end() }) test('mismatched parens', function (t) { t.throws(function () { parse('(&(foo=bar)(!(state=done))') }) t.end() }) node-ldapjs-2.3.3/test/filters/presence.test.js000066400000000000000000000031321424777304200214770ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { filters: { PresenceFilter } } = require('../../lib') test('Construct no args', function (t) { const f = new PresenceFilter() t.ok(f) t.ok(!f.attribute) t.end() }) test('Construct args', function (t) { const f = new PresenceFilter({ attribute: 'foo' }) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.toString(), '(foo=*)') t.end() }) test('GH-109 = escape value only in toString()', function (t) { const f = new PresenceFilter({ attribute: 'fo)o' }) t.ok(f) t.equal(f.attribute, 'fo)o') t.equal(f.toString(), '(fo\\29o=*)') t.end() }) test('match true', function (t) { const f = new PresenceFilter({ attribute: 'foo' }) t.ok(f) t.ok(f.matches({ foo: 'bar' })) t.end() }) test('match false', function (t) { const f = new PresenceFilter({ attribute: 'foo' }) t.ok(f) t.ok(!f.matches({ bar: 'foo' })) t.end() }) test('parse ok', function (t) { const writer = new BerWriter() writer.writeString('foo', 0x87) const f = new PresenceFilter() t.ok(f) const reader = new BerReader(writer.buffer) reader.readSequence() t.ok(f.parse(reader)) t.ok(f.matches({ foo: 'bar' })) t.end() }) test('GH-109 = to ber uses plain values', function (t) { let f = new PresenceFilter({ attribute: 'f(o)o' }) t.ok(f) const writer = new BerWriter() f.toBer(writer) f = new PresenceFilter() t.ok(f) const reader = new BerReader(writer.buffer) reader.readSequence() t.ok(f.parse(reader)) t.equal(f.attribute, 'f(o)o') t.end() }) node-ldapjs-2.3.3/test/filters/substr.test.js000066400000000000000000000064161424777304200212250ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { filters: { SubstringFilter } } = require('../../lib') test('Construct no args', function (t) { const f = new SubstringFilter() t.ok(f) t.ok(!f.attribute) t.ok(!f.value) t.end() }) test('Construct args', function (t) { const f = new SubstringFilter({ attribute: 'foo', initial: 'bar', any: ['zig', 'zag'], final: 'baz' }) t.ok(f) t.equal(f.attribute, 'foo') t.equal(f.initial, 'bar') t.equal(f.any.length, 2) t.equal(f.any[0], 'zig') t.equal(f.any[1], 'zag') t.equal(f.final, 'baz') t.equal(f.toString(), '(foo=bar*zig*zag*baz)') t.end() }) test('GH-109 = escape value only in toString()', function (t) { const f = new SubstringFilter({ attribute: 'fo(o', initial: 'ba(r)', any: ['zi)g', 'z(ag'], final: '(baz)' }) t.ok(f) t.equal(f.attribute, 'fo(o') t.equal(f.initial, 'ba(r)') t.equal(f.any.length, 2) t.equal(f.any[0], 'zi)g') t.equal(f.any[1], 'z(ag') t.equal(f.final, '(baz)') t.equal(f.toString(), '(fo\\28o=ba\\28r\\29*zi\\29g*z\\28ag*\\28baz\\29)') t.end() }) test('match true', function (t) { const f = new SubstringFilter({ attribute: 'foo', initial: 'bar', any: ['zig', 'zag'], final: 'baz' }) t.ok(f) t.ok(f.matches({ foo: 'barmoozigbarzagblahbaz' })) t.end() }) test('match false', function (t) { const f = new SubstringFilter({ attribute: 'foo', initial: 'bar', foo: ['zig', 'zag'], final: 'baz' }) t.ok(f) t.ok(!f.matches({ foo: 'bafmoozigbarzagblahbaz' })) t.end() }) test('match any', function (t) { const f = new SubstringFilter({ attribute: 'foo', initial: 'bar' }) t.ok(f) t.ok(f.matches({ foo: ['beuha', 'barista'] })) t.end() }) test('GH-109 = escape for regex in matches', function (t) { const f = new SubstringFilter({ attribute: 'fo(o', initial: 'ba(r)', any: ['zi)g', 'z(ag'], final: '(baz)' }) t.ok(f) t.ok(f.matches({ 'fo(o': ['ba(r)_zi)g-z(ag~(baz)'] })) t.end() }) test('parse ok', function (t) { const writer = new BerWriter() writer.writeString('foo') writer.startSequence() writer.writeString('bar', 0x80) writer.writeString('bad', 0x81) writer.writeString('baz', 0x82) writer.endSequence() const f = new SubstringFilter() t.ok(f) t.ok(f.parse(new BerReader(writer.buffer))) t.ok(f.matches({ foo: 'bargoobadgoobaz' })) t.end() }) test('parse bad', function (t) { const writer = new BerWriter() writer.writeString('foo') writer.writeInt(20) const f = new SubstringFilter() t.ok(f) try { f.parse(new BerReader(writer.buffer)) t.fail('Should have thrown InvalidAsn1Error') } catch (e) { } t.end() }) test('GH-109 = to ber uses plain values', function (t) { let f = new SubstringFilter({ attribute: 'fo(o', initial: 'ba(r)', any: ['zi)g', 'z(ag'], final: '(baz)' }) t.ok(f) const writer = new BerWriter() f.toBer(writer) f = new SubstringFilter() t.ok(f) const reader = new BerReader(writer.buffer) reader.readSequence() t.ok(f.parse(reader)) t.equal(f.attribute, 'fo(o') t.equal(f.initial, 'ba(r)') t.equal(f.any.length, 2) t.equal(f.any[0], 'zi)g') t.equal(f.any[1], 'z(ag') t.equal(f.final, '(baz)') t.end() }) node-ldapjs-2.3.3/test/imgs/000077500000000000000000000000001424777304200156475ustar00rootroot00000000000000node-ldapjs-2.3.3/test/imgs/test.jpg000066400000000000000000000013441424777304200173320ustar00rootroot00000000000000ÿØÿàJFIFÿþ>CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), default quality ÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀ "ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?ðÙ´ÉckÒŒ¯¤…$sÆ9À8÷#gßj HÍAíƒþd$“#’I'æ=O_Ι@ÿÙnode-ldapjs-2.3.3/test/laundry.test.js000066400000000000000000000072741424777304200177140ustar00rootroot00000000000000'use strict' const tap = require('tap') const { getSock, uuid } = require('./utils') const ldap = require('../lib') function search (t, options, callback) { t.context.client.search(t.context.suffix, options, function (err, res) { t.error(err) t.ok(res) let found = false res.on('searchEntry', function (entry) { t.ok(entry) found = true }) res.on('end', function () { t.ok(found) if (callback) return callback() return t.end() }) }) } tap.beforeEach((t) => { return new Promise((resolve, reject) => { const suffix = `dc=${uuid()}` const server = ldap.createServer() t.context.server = server t.context.socketPath = getSock() t.context.suffix = suffix server.bind('cn=root', function (req, res, next) { res.end() return next() }) server.search(suffix, function (req, res) { const entry = { dn: 'cn=foo, ' + suffix, attributes: { objectclass: ['person', 'top'], cn: 'Pogo Stick', sn: 'Stick', givenname: 'ogo', mail: uuid() + '@pogostick.org' } } if (req.filter.matches(entry.attributes)) { res.send(entry) } res.end() }) server.listen(t.context.socketPath, function () { t.context.client = ldap.createClient({ socketPath: t.context.socketPath }) t.context.client.on('connectError', (err) => { t.context.server.close(() => reject(err)) }) t.context.client.on('connect', (socket) => { t.context.socket = socket resolve() }) }) }) }) tap.afterEach((t) => { return new Promise((resolve, reject) => { if (!t.context.client) return resolve() t.context.client.unbind(() => { t.context.server.close((err) => { if (err) return reject(err) resolve() }) }) }) }) tap.test('Evolution search filter (GH-3)', function (t) { // This is what Evolution sends, when searching for a contact 'ogo'. Wow. const filter = '(|(cn=ogo*)(givenname=ogo*)(sn=ogo*)(mail=ogo*)(member=ogo*)' + '(primaryphone=ogo*)(telephonenumber=ogo*)(homephone=ogo*)(mobile=ogo*)' + '(carphone=ogo*)(facsimiletelephonenumber=ogo*)' + '(homefacsimiletelephonenumber=ogo*)(otherphone=ogo*)' + '(otherfacsimiletelephonenumber=ogo*)(internationalisdnnumber=ogo*)' + '(pager=ogo*)(radio=ogo*)(telex=ogo*)(assistantphone=ogo*)' + '(companyphone=ogo*)(callbackphone=ogo*)(tty=ogo*)(o=ogo*)(ou=ogo*)' + '(roomnumber=ogo*)(title=ogo*)(businessrole=ogo*)(managername=ogo*)' + '(assistantname=ogo*)(postaladdress=ogo*)(l=ogo*)(st=ogo*)' + '(postofficebox=ogo*)(postalcode=ogo*)(c=ogo*)(homepostaladdress=ogo*)' + '(mozillahomelocalityname=ogo*)(mozillahomestate=ogo*)' + '(mozillahomepostalcode=ogo*)(mozillahomecountryname=ogo*)' + '(otherpostaladdress=ogo*)(jpegphoto=ogo*)(usercertificate=ogo*)' + '(labeleduri=ogo*)(displayname=ogo*)(spousename=ogo*)(note=ogo*)' + '(anniversary=ogo*)(birthdate=ogo*)(mailer=ogo*)(fileas=ogo*)' + '(category=ogo*)(calcaluri=ogo*)(calfburl=ogo*)(icscalendar=ogo*))' return search(t, filter) }) tap.test('GH-49 Client errors on bad attributes', function (t) { const searchOpts = { filter: 'cn=*ogo*', scope: 'one', attributes: 'dn' } return search(t, searchOpts) }) tap.test('GH-55 Client emits connect multiple times', function (t) { const c = ldap.createClient({ socketPath: t.context.socketPath }) let count = 0 c.on('connect', function (socket) { t.ok(socket) count++ c.bind('cn=root', 'secret', function (err) { t.ifError(err) c.unbind(function () { t.equal(count, 1) t.end() }) }) }) }) node-ldapjs-2.3.3/test/lib/000077500000000000000000000000001424777304200154565ustar00rootroot00000000000000node-ldapjs-2.3.3/test/lib/client/000077500000000000000000000000001424777304200167345ustar00rootroot00000000000000node-ldapjs-2.3.3/test/lib/client/message-tracker/000077500000000000000000000000001424777304200220115ustar00rootroot00000000000000node-ldapjs-2.3.3/test/lib/client/message-tracker/ge-window.test.js000066400000000000000000000025331424777304200252300ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { MAX_MSGID } = require('../../../../lib/client/constants') const geWindow = require('../../../../lib/client/message-tracker/ge-window') test('comp > (ref in upper window) => true', async t => { const ref = Math.floor(MAX_MSGID / 2) + 10 const comp = ref + 10 const result = geWindow(ref, comp) t.equal(result, true) }) test('comp < (ref in upper window) => false', async t => { const ref = Math.floor(MAX_MSGID / 2) + 10 const comp = ref - 5 const result = geWindow(ref, comp) t.equal(result, false) }) test('comp > (ref in lower window) => true', async t => { const ref = Math.floor(MAX_MSGID / 2) - 10 const comp = ref + 20 const result = geWindow(ref, comp) t.equal(result, true) }) test('comp < (ref in lower window) => false', async t => { const ref = Math.floor(MAX_MSGID / 2) - 10 const comp = ref - 5 const result = geWindow(ref, comp) t.equal(result, false) }) test('(max === MAX_MSGID) && (comp > ref) => true', async t => { const ref = MAX_MSGID - Math.floor(MAX_MSGID / 2) const comp = ref + 1 const result = geWindow(ref, comp) t.equal(result, true) }) test('(max === MAX_MSGID) && (comp < ref) => false', async t => { const ref = MAX_MSGID - Math.floor(MAX_MSGID / 2) const comp = ref - 1 const result = geWindow(ref, comp) t.equal(result, false) }) node-ldapjs-2.3.3/test/lib/client/message-tracker/id-generator.test.js000066400000000000000000000010361424777304200257050ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { MAX_MSGID } = require('../../../../lib/client/constants') const idGeneratorFactory = require('../../../../lib/client/message-tracker/id-generator') test('starts at 0', async t => { const nextID = idGeneratorFactory() const currentID = nextID() t.equal(currentID, 1) }) test('handles wrapping around', async t => { const nextID = idGeneratorFactory(MAX_MSGID - 2) let currentID = nextID() t.equal(currentID, MAX_MSGID - 1) currentID = nextID() t.equal(currentID, 1) }) node-ldapjs-2.3.3/test/lib/client/message-tracker/index.test.js000066400000000000000000000116241424777304200244400ustar00rootroot00000000000000'use strict' const tap = require('tap') const messageTrackerFactory = require('../../../../lib/client/message-tracker/') tap.test('options', t => { t.test('requires an options object', async t => { try { messageTrackerFactory() } catch (error) { t.match(error, /options object is required/) } try { messageTrackerFactory([]) } catch (error) { t.match(error, /options object is required/) } try { messageTrackerFactory('') } catch (error) { t.match(error, /options object is required/) } try { messageTrackerFactory(42) } catch (error) { t.match(error, /options object is required/) } }) t.test('requires id to be a string', async t => { try { messageTrackerFactory({ id: {} }) } catch (error) { t.match(error, /options\.id string is required/) } try { messageTrackerFactory({ id: [] }) } catch (error) { t.match(error, /options\.id string is required/) } try { messageTrackerFactory({ id: 42 }) } catch (error) { t.match(error, /options\.id string is required/) } }) t.test('requires parser to be an object', async t => { try { messageTrackerFactory({ id: 'foo', parser: 'bar' }) } catch (error) { t.match(error, /options\.parser object is required/) } try { messageTrackerFactory({ id: 'foo', parser: 42 }) } catch (error) { t.match(error, /options\.parser object is required/) } try { messageTrackerFactory({ id: 'foo', parser: [] }) } catch (error) { t.match(error, /options\.parser object is required/) } }) t.end() }) tap.test('.pending', t => { t.test('returns 0 for no messages', async t => { const tracker = messageTrackerFactory({ id: 'foo', parser: {} }) t.equal(tracker.pending, 0) }) t.test('returns 1 for 1 message', async t => { const tracker = messageTrackerFactory({ id: 'foo', parser: {} }) tracker.track({}, () => {}) t.equal(tracker.pending, 1) }) t.end() }) tap.test('#abandon', t => { t.test('returns false if message does not exist', async t => { const tracker = messageTrackerFactory({ id: 'foo', parser: {} }) const result = tracker.abandon(1) t.equal(result, false) }) t.test('returns true if message is abandoned', async t => { const tracker = messageTrackerFactory({ id: 'foo', parser: {} }) tracker.track({}, {}) const result = tracker.abandon(1) t.equal(result, true) }) t.end() }) tap.test('#fetch', t => { t.test('returns handler for fetched message', async t => { const tracker = messageTrackerFactory({ id: 'foo', parser: {} }) tracker.track({}, handler) const fetched = tracker.fetch(1) t.equal(fetched, handler) function handler () {} }) t.test('returns handler for fetched abandoned message', async t => { const tracker = messageTrackerFactory({ id: 'foo', parser: {} }) tracker.track({}, handler) tracker.track({ abandon: 'message' }, () => {}) tracker.abandon(1) const fetched = tracker.fetch(1) t.equal(fetched, handler) function handler () {} }) t.test('returns null when message does not exist', async t => { const tracker = messageTrackerFactory({ id: 'foo', parser: {} }) const fetched = tracker.fetch(1) t.equal(fetched, null) }) t.end() }) tap.test('#purge', t => { t.test('invokes cb for each tracked message', async t => { t.plan(4) let count = 0 const tracker = messageTrackerFactory({ id: 'foo', parser: {} }) tracker.track({}, handler1) tracker.track({}, handler2) tracker.purge(cb) function cb (msgID, handler) { if (count === 0) { t.equal(msgID, 1) t.equal(handler, handler1) count += 1 return } t.equal(msgID, 2) t.equal(handler, handler2) } function handler1 () {} function handler2 () {} }) t.end() }) tap.test('#remove', t => { t.test('removes from the current track', async t => { const tracker = messageTrackerFactory({ id: 'foo', parser: {} }) tracker.track({}, () => {}) tracker.remove(1) t.equal(tracker.pending, 0) }) // Not a great test. It exercises the desired code path, but we probably // should expose some insight into the abandoned track. t.test('removes from the abandoned track', async t => { const tracker = messageTrackerFactory({ id: 'foo', parser: {} }) tracker.track({}, () => {}) tracker.track({ abandon: 'message' }, () => {}) tracker.abandon(1) tracker.remove(1) t.equal(tracker.pending, 1) }) t.end() }) tap.test('#track', t => { t.test('add messageID and tracks message', async t => { const tracker = messageTrackerFactory({ id: 'foo', parser: {} }) const msg = {} tracker.track(msg, handler) t.same(msg, { messageID: 1 }) const cb = tracker.fetch(1) t.equal(cb, handler) function handler () {} }) t.end() }) node-ldapjs-2.3.3/test/lib/client/message-tracker/purge-abandoned.test.js000066400000000000000000000031741424777304200263650ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { MAX_MSGID } = require('../../../../lib/client/constants') const purgeAbandoned = require('../../../../lib/client/message-tracker/purge-abandoned') test('clears queue if only one message present', async t => { t.plan(3) const abandoned = new Map() abandoned.set(1, { age: 2, cb }) purgeAbandoned(2, abandoned) t.equal(abandoned.size, 0) function cb (err) { t.equal(err.name, 'AbandonedError') t.equal(err.message, 'client request abandoned') } }) test('clears queue if multiple messages present', async t => { t.plan(5) const abandoned = new Map() abandoned.set(1, { age: 2, cb }) abandoned.set(2, { age: 3, cb }) purgeAbandoned(4, abandoned) t.equal(abandoned.size, 0) function cb (err) { t.equal(err.name, 'AbandonedError') t.equal(err.message, 'client request abandoned') } }) test('message id has wrappred around', async t => { t.plan(3) const abandoned = new Map() abandoned.set(MAX_MSGID - 1, { age: MAX_MSGID, cb }) // The "abandon" message was sent with an id of "MAX_MSGID". So the message // that is triggering the purge was the "first" message in the new sequence // of message identifiers. purgeAbandoned(1, abandoned) t.equal(abandoned.size, 0) function cb (err) { t.equal(err.name, 'AbandonedError') t.equal(err.message, 'client request abandoned') } }) test('does not clear if window not met', async t => { t.plan(1) const abandoned = new Map() abandoned.set(1, { age: 2, cb }) purgeAbandoned(1, abandoned) t.equal(abandoned.size, 1) function cb () { t.fail('should not be invoked') } }) node-ldapjs-2.3.3/test/lib/client/request-queue/000077500000000000000000000000001424777304200215465ustar00rootroot00000000000000node-ldapjs-2.3.3/test/lib/client/request-queue/enqueue.test.js000066400000000000000000000034561424777304200245410ustar00rootroot00000000000000'use strict' const { test } = require('tap') const enqueue = require('../../../../lib/client/request-queue/enqueue') test('rejects new requests if size is exceeded', async t => { const q = { _queue: { length: 5 }, size: 5 } const result = enqueue.call(q, 'foo', 'bar', {}, {}) t.notOk(result) }) test('rejects new requests if queue is frozen', async t => { const q = { _queue: { length: 0 }, size: 5, _frozen: true } const result = enqueue.call(q, 'foo', 'bar', {}, {}) t.notOk(result) }) test('adds a request and returns if no timeout', async t => { const q = { _queue: { length: 0, add (obj) { t.same(obj, { message: 'foo', expect: 'bar', emitter: 'baz', cb: 'bif' }) } }, _frozen: false, timeout: 0 } const result = enqueue.call(q, 'foo', 'bar', 'baz', 'bif') t.ok(result) }) test('adds a request and returns timer not set', async t => { const q = { _queue: { length: 0, add (obj) { t.same(obj, { message: 'foo', expect: 'bar', emitter: 'baz', cb: 'bif' }) } }, _frozen: false, timeout: 100, _timer: null } const result = enqueue.call(q, 'foo', 'bar', 'baz', 'bif') t.ok(result) }) test('adds a request, returns true, and clears queue', t => { // Must not be an async test due to an internal `setTimeout` t.plan(4) const q = { _queue: { length: 0, add (obj) { t.same(obj, { message: 'foo', expect: 'bar', emitter: 'baz', cb: 'bif' }) } }, _frozen: false, timeout: 5, _timer: 123, freeze () { t.pass() }, purge () { t.pass() } } const result = enqueue.call(q, 'foo', 'bar', 'baz', 'bif') t.ok(result) }) node-ldapjs-2.3.3/test/lib/client/request-queue/flush.test.js000066400000000000000000000015451424777304200242100ustar00rootroot00000000000000'use strict' const { test } = require('tap') const flush = require('../../../../lib/client/request-queue/flush') test('clears timer', async t => { t.plan(2) const q = { _timer: 123, _queue: { values () { return [] }, clear () { t.pass() } } } flush.call(q) t.equal(q._timer, null) }) test('invokes callback with parameters', async t => { t.plan(6) const req = { message: 'foo', expect: 'bar', emitter: 'baz', cb: theCB } const q = { _timer: 123, _queue: { values () { return [req] }, clear () { t.pass() } } } flush.call(q, (message, expect, emitter, cb) => { t.equal(message, 'foo') t.equal(expect, 'bar') t.equal(emitter, 'baz') t.equal(cb, theCB) }) t.equal(q._timer, null) function theCB () {} }) node-ldapjs-2.3.3/test/lib/client/request-queue/purge.test.js000066400000000000000000000006301424777304200242030ustar00rootroot00000000000000'use strict' const { test } = require('tap') const purge = require('../../../../lib/client/request-queue/purge') test('flushes the queue with timeout errors', async t => { t.plan(3) const q = { flush (func) { func('a', 'b', 'c', (err) => { t.ok(err) t.equal(err.name, 'TimeoutError') t.equal(err.message, 'request queue timeout') }) } } purge.call(q) }) node-ldapjs-2.3.3/test/messages/000077500000000000000000000000001424777304200165175ustar00rootroot00000000000000node-ldapjs-2.3.3/test/messages/add_request.test.js000066400000000000000000000057361424777304200223460ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { AddRequest, Attribute, dn } = require('../../lib') test('new no args', t => { t.ok(new AddRequest()) t.end() }) test('new with args', t => { const req = new AddRequest({ entry: dn.parse('cn=foo, o=test'), attributes: [new Attribute({ type: 'cn', vals: ['foo'] }), new Attribute({ type: 'objectclass', vals: ['person'] })] }) t.ok(req) t.equal(req.dn.toString(), 'cn=foo, o=test') t.equal(req.attributes.length, 2) t.equal(req.attributes[0].type, 'cn') t.equal(req.attributes[0].vals[0], 'foo') t.equal(req.attributes[1].type, 'objectclass') t.equal(req.attributes[1].vals[0], 'person') t.end() }) test('parse', t => { const ber = new BerWriter() ber.writeString('cn=foo, o=test') ber.startSequence() ber.startSequence() ber.writeString('cn') ber.startSequence(0x31) ber.writeString('foo') ber.endSequence() ber.endSequence() ber.startSequence() ber.writeString('objectclass') ber.startSequence(0x31) ber.writeString('person') ber.endSequence() ber.endSequence() ber.endSequence() const req = new AddRequest() t.ok(req._parse(new BerReader(ber.buffer))) t.equal(req.dn.toString(), 'cn=foo, o=test') t.equal(req.attributes.length, 2) t.equal(req.attributes[0].type, 'cn') t.equal(req.attributes[0].vals[0], 'foo') t.equal(req.attributes[1].type, 'objectclass') t.equal(req.attributes[1].vals[0], 'person') t.end() }) test('toBer', t => { const req = new AddRequest({ messageID: 123, entry: dn.parse('cn=foo, o=test'), attributes: [new Attribute({ type: 'cn', vals: ['foo'] }), new Attribute({ type: 'objectclass', vals: ['person'] })] }) t.ok(req) const ber = new BerReader(req.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x68) t.equal(ber.readString(), 'cn=foo, o=test') t.ok(ber.readSequence()) t.ok(ber.readSequence()) t.equal(ber.readString(), 'cn') t.equal(ber.readSequence(), 0x31) t.equal(ber.readString(), 'foo') t.ok(ber.readSequence()) t.equal(ber.readString(), 'objectclass') t.equal(ber.readSequence(), 0x31) t.equal(ber.readString(), 'person') t.end() }) test('toObject', t => { const req = new AddRequest({ entry: dn.parse('cn=foo, o=test'), attributes: [new Attribute({ type: 'cn', vals: ['foo', 'bar'] }), new Attribute({ type: 'objectclass', vals: ['person'] })] }) t.ok(req) const obj = req.toObject() t.ok(obj) t.ok(obj.dn) t.equal(obj.dn, 'cn=foo, o=test') t.ok(obj.attributes) t.ok(obj.attributes.cn) t.ok(Array.isArray(obj.attributes.cn)) t.equal(obj.attributes.cn.length, 2) t.equal(obj.attributes.cn[0], 'foo') t.equal(obj.attributes.cn[1], 'bar') t.ok(obj.attributes.objectclass) t.ok(Array.isArray(obj.attributes.objectclass)) t.equal(obj.attributes.objectclass.length, 1) t.equal(obj.attributes.objectclass[0], 'person') t.end() }) node-ldapjs-2.3.3/test/messages/add_response.test.js000066400000000000000000000022301424777304200224760ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { AddResponse } = require('../../lib') test('new no args', function (t) { t.ok(new AddResponse()) t.end() }) test('new with args', function (t) { const res = new AddResponse({ messageID: 123, status: 0 }) t.ok(res) t.equal(res.messageID, 123) t.equal(res.status, 0) t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.writeEnumeration(0) ber.writeString('cn=root') ber.writeString('foo') const res = new AddResponse() t.ok(res._parse(new BerReader(ber.buffer))) t.equal(res.status, 0) t.equal(res.matchedDN, 'cn=root') t.equal(res.errorMessage, 'foo') t.end() }) test('toBer', function (t) { const res = new AddResponse({ messageID: 123, status: 3, matchedDN: 'cn=root', errorMessage: 'foo' }) t.ok(res) const ber = new BerReader(res.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x69) t.equal(ber.readEnumeration(), 3) t.equal(ber.readString(), 'cn=root') t.equal(ber.readString(), 'foo') t.end() }) node-ldapjs-2.3.3/test/messages/bind_request.test.js000066400000000000000000000024071424777304200225220ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { BindRequest, dn } = require('../../lib') test('new no args', function (t) { t.ok(new BindRequest()) t.end() }) test('new with args', function (t) { const req = new BindRequest({ version: 3, name: dn.parse('cn=root'), credentials: 'secret' }) t.ok(req) t.equal(req.version, 3) t.equal(req.name.toString(), 'cn=root') t.equal(req.credentials, 'secret') t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.writeInt(3) ber.writeString('cn=root') ber.writeString('secret', 0x80) const req = new BindRequest() t.ok(req._parse(new BerReader(ber.buffer))) t.equal(req.version, 3) t.equal(req.dn.toString(), 'cn=root') t.equal(req.credentials, 'secret') t.end() }) test('toBer', function (t) { const req = new BindRequest({ messageID: 123, version: 3, name: dn.parse('cn=root'), credentials: 'secret' }) t.ok(req) const ber = new BerReader(req.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x60) t.equal(ber.readInt(), 0x03) t.equal(ber.readString(), 'cn=root') t.equal(ber.readString(0x80), 'secret') t.end() }) node-ldapjs-2.3.3/test/messages/bind_response.test.js000066400000000000000000000022351424777304200226670ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { BindResponse } = require('../../lib') test('new no args', function (t) { t.ok(new BindResponse()) t.end() }) test('new with args', function (t) { const res = new BindResponse({ messageID: 123, status: 0 }) t.ok(res) t.equal(res.messageID, 123) t.equal(res.status, 0) t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.writeEnumeration(0) ber.writeString('cn=root') ber.writeString('foo') const res = new BindResponse() t.ok(res._parse(new BerReader(ber.buffer))) t.equal(res.status, 0) t.equal(res.matchedDN, 'cn=root') t.equal(res.errorMessage, 'foo') t.end() }) test('toBer', function (t) { const res = new BindResponse({ messageID: 123, status: 3, matchedDN: 'cn=root', errorMessage: 'foo' }) t.ok(res) const ber = new BerReader(res.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x61) t.equal(ber.readEnumeration(), 3) t.equal(ber.readString(), 'cn=root') t.equal(ber.readString(), 'foo') t.end() }) node-ldapjs-2.3.3/test/messages/compare_request.test.js000066400000000000000000000025621424777304200232360ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { CompareRequest, dn } = require('../../lib') test('new no args', function (t) { t.ok(new CompareRequest()) t.end() }) test('new with args', function (t) { const req = new CompareRequest({ entry: dn.parse('cn=foo, o=test'), attribute: 'sn', value: 'testy' }) t.ok(req) t.equal(req.dn.toString(), 'cn=foo, o=test') t.equal(req.attribute, 'sn') t.equal(req.value, 'testy') t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.writeString('cn=foo, o=test') ber.startSequence() ber.writeString('sn') ber.writeString('testy') ber.endSequence() const req = new CompareRequest() t.ok(req._parse(new BerReader(ber.buffer))) t.equal(req.dn, 'cn=foo, o=test') t.equal(req.attribute, 'sn') t.equal(req.value, 'testy') t.end() }) test('toBer', function (t) { const req = new CompareRequest({ messageID: 123, entry: dn.parse('cn=foo, o=test'), attribute: 'sn', value: 'testy' }) t.ok(req) const ber = new BerReader(req.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x6e) t.equal(ber.readString(), 'cn=foo, o=test') t.ok(ber.readSequence()) t.equal(ber.readString(), 'sn') t.equal(ber.readString(), 'testy') t.end() }) node-ldapjs-2.3.3/test/messages/compare_response.test.js000066400000000000000000000022541424777304200234020ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { CompareResponse } = require('../../lib') test('new no args', function (t) { t.ok(new CompareResponse()) t.end() }) test('new with args', function (t) { const res = new CompareResponse({ messageID: 123, status: 0 }) t.ok(res) t.equal(res.messageID, 123) t.equal(res.status, 0) t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.writeEnumeration(0) ber.writeString('cn=root') ber.writeString('foo') const res = new CompareResponse() t.ok(res._parse(new BerReader(ber.buffer))) t.equal(res.status, 0) t.equal(res.matchedDN, 'cn=root') t.equal(res.errorMessage, 'foo') t.end() }) test('toBer', function (t) { const res = new CompareResponse({ messageID: 123, status: 3, matchedDN: 'cn=root', errorMessage: 'foo' }) t.ok(res) const ber = new BerReader(res.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x6f) t.equal(ber.readEnumeration(), 3) t.equal(ber.readString(), 'cn=root') t.equal(ber.readString(), 'foo') t.end() }) node-ldapjs-2.3.3/test/messages/del_request.test.js000066400000000000000000000017501424777304200223520ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { DeleteRequest, dn } = require('../../lib') test('new no args', function (t) { t.ok(new DeleteRequest()) t.end() }) test('new with args', function (t) { const req = new DeleteRequest({ entry: dn.parse('cn=test') }) t.ok(req) t.equal(req.dn.toString(), 'cn=test') t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.writeString('cn=test', 0x4a) const req = new DeleteRequest() const reader = new BerReader(ber.buffer) reader.readSequence(0x4a) t.ok(req.parse(reader, reader.length)) t.equal(req.dn.toString(), 'cn=test') t.end() }) test('toBer', function (t) { const req = new DeleteRequest({ messageID: 123, entry: dn.parse('cn=test') }) t.ok(req) const ber = new BerReader(req.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readString(0x4a), 'cn=test') t.end() }) node-ldapjs-2.3.3/test/messages/del_response.test.js000066400000000000000000000022471424777304200225220ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { DeleteResponse } = require('../../lib') test('new no args', function (t) { t.ok(new DeleteResponse()) t.end() }) test('new with args', function (t) { const res = new DeleteResponse({ messageID: 123, status: 0 }) t.ok(res) t.equal(res.messageID, 123) t.equal(res.status, 0) t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.writeEnumeration(0) ber.writeString('cn=root') ber.writeString('foo') const res = new DeleteResponse() t.ok(res._parse(new BerReader(ber.buffer))) t.equal(res.status, 0) t.equal(res.matchedDN, 'cn=root') t.equal(res.errorMessage, 'foo') t.end() }) test('toBer', function (t) { const res = new DeleteResponse({ messageID: 123, status: 3, matchedDN: 'cn=root', errorMessage: 'foo' }) t.ok(res) const ber = new BerReader(res.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x6b) t.equal(ber.readEnumeration(), 3) t.equal(ber.readString(), 'cn=root') t.equal(ber.readString(), 'foo') t.end() }) node-ldapjs-2.3.3/test/messages/ext_request.test.js000066400000000000000000000063751424777304200224160ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { ExtendedRequest } = require('../../lib') test('new no args', function (t) { t.ok(new ExtendedRequest()) t.end() }) test('new with args', function (t) { const req = new ExtendedRequest({ requestName: '1.2.3.4', requestValue: 'test' }) t.ok(req) t.equal(req.requestName, '1.2.3.4') t.equal(req.requestValue, 'test') t.equal(Buffer.compare(req.requestValueBuffer, Buffer.from('test', 'utf8')), 0) t.equal(req.value, 'test') t.equal(Buffer.compare(req.valueBuffer, Buffer.from('test', 'utf8')), 0) t.end() }) test('new with buffer args', function (t) { const req = new ExtendedRequest({ requestName: '1.2.3.4', requestValue: Buffer.from('test', 'utf8') }) t.ok(req) t.equal(req.requestName, '1.2.3.4') t.equal(req.requestValue, req.requestValueBuffer) t.equal(Buffer.compare(req.requestValueBuffer, Buffer.from('test', 'utf8')), 0) t.equal(req.value, req.valueBuffer) t.equal(Buffer.compare(req.valueBuffer, Buffer.from('test', 'utf8')), 0) t.end() }) test('new no args set args', function (t) { const req = new ExtendedRequest() t.ok(req) req.name = '1.2.3.4' t.equal(req.requestName, '1.2.3.4') req.value = 'test' t.equal(req.requestValue, 'test') t.equal(Buffer.compare(req.requestValueBuffer, Buffer.from('test', 'utf8')), 0) t.equal(req.value, 'test') t.equal(Buffer.compare(req.valueBuffer, Buffer.from('test', 'utf8')), 0) t.end() }) test('new no args set args buffer', function (t) { const req = new ExtendedRequest() t.ok(req) req.name = '1.2.3.4' t.equal(req.requestName, '1.2.3.4') req.value = Buffer.from('test', 'utf8') t.equal(req.requestValue, req.requestValueBuffer) t.equal(Buffer.compare(req.requestValueBuffer, Buffer.from('test', 'utf8')), 0) t.equal(req.value, req.valueBuffer) t.equal(Buffer.compare(req.valueBuffer, Buffer.from('test', 'utf8')), 0) t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.writeString('1.2.3.4', 0x80) ber.writeString('test', 0x81) const req = new ExtendedRequest() t.ok(req._parse(new BerReader(ber.buffer))) t.equal(req.requestName, '1.2.3.4') t.equal(req.requestValue, 'test') t.equal(Buffer.compare(req.requestValueBuffer, Buffer.from('test', 'utf8')), 0) t.equal(req.value, 'test') t.equal(Buffer.compare(req.valueBuffer, Buffer.from('test', 'utf8')), 0) t.end() }) test('toBer', function (t) { const req = new ExtendedRequest({ messageID: 123, requestName: '1.2.3.4', requestValue: 'test' }) t.ok(req) const ber = new BerReader(req.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x77) t.equal(ber.readString(0x80), '1.2.3.4') t.equal(ber.readString(0x81), 'test') t.end() }) test('toBer from buffer', function (t) { const req = new ExtendedRequest({ messageID: 123, requestName: '1.2.3.4', requestValue: Buffer.from('test', 'utf8') }) t.ok(req) const ber = new BerReader(req.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x77) t.equal(ber.readString(0x80), '1.2.3.4') t.equal(ber.readString(0x81), 'test') t.end() }) node-ldapjs-2.3.3/test/messages/ext_response.test.js000066400000000000000000000031171424777304200225530ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { ExtendedResponse } = require('../../lib') test('new no args', function (t) { t.ok(new ExtendedResponse()) t.end() }) test('new with args', function (t) { const res = new ExtendedResponse({ messageID: 123, status: 0, responseName: '1.2.3.4', responseValue: 'test' }) t.ok(res) t.equal(res.messageID, 123) t.equal(res.status, 0) t.equal(res.responseName, '1.2.3.4') t.equal(res.responseValue, 'test') t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.writeEnumeration(0) ber.writeString('cn=root') ber.writeString('foo') ber.writeString('1.2.3.4', 0x8a) ber.writeString('test', 0x8b) const res = new ExtendedResponse() t.ok(res._parse(new BerReader(ber.buffer))) t.equal(res.status, 0) t.equal(res.matchedDN, 'cn=root') t.equal(res.errorMessage, 'foo') t.equal(res.responseName, '1.2.3.4') t.equal(res.responseValue, 'test') t.end() }) test('toBer', function (t) { const res = new ExtendedResponse({ messageID: 123, status: 3, matchedDN: 'cn=root', errorMessage: 'foo', responseName: '1.2.3.4', responseValue: 'test' }) t.ok(res) const ber = new BerReader(res.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x78) t.equal(ber.readEnumeration(), 3) t.equal(ber.readString(), 'cn=root') t.equal(ber.readString(), 'foo') t.equal(ber.readString(0x8a), '1.2.3.4') t.equal(ber.readString(0x8b), 'test') t.end() }) node-ldapjs-2.3.3/test/messages/moddn_request.test.js000066400000000000000000000026041424777304200227060ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { ModifyDNRequest, dn } = require('../../lib') test('new no args', function (t) { t.ok(new ModifyDNRequest()) t.end() }) test('new with args', function (t) { const req = new ModifyDNRequest({ entry: dn.parse('cn=foo, o=test'), newRdn: dn.parse('cn=foo2'), deleteOldRdn: true }) t.ok(req) t.equal(req.dn.toString(), 'cn=foo, o=test') t.equal(req.newRdn.toString(), 'cn=foo2') t.equal(req.deleteOldRdn, true) t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.writeString('cn=foo, o=test') ber.writeString('cn=foo2') ber.writeBoolean(true) const req = new ModifyDNRequest() t.ok(req._parse(new BerReader(ber.buffer))) t.equal(req.dn.toString(), 'cn=foo, o=test') t.equal(req.newRdn.toString(), 'cn=foo2') t.equal(req.deleteOldRdn, true) t.end() }) test('toBer', function (t) { const req = new ModifyDNRequest({ messageID: 123, entry: dn.parse('cn=foo, o=test'), newRdn: dn.parse('cn=foo2'), deleteOldRdn: true }) t.ok(req) const ber = new BerReader(req.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x6c) t.equal(ber.readString(), 'cn=foo, o=test') t.equal(ber.readString(), 'cn=foo2') t.equal(ber.readBoolean(), true) t.end() }) node-ldapjs-2.3.3/test/messages/moddn_response.test.js000066400000000000000000000022611424777304200230530ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { ModifyDNResponse } = require('../../lib') test('new no args', function (t) { t.ok(new ModifyDNResponse()) t.end() }) test('new with args', function (t) { const res = new ModifyDNResponse({ messageID: 123, status: 0 }) t.ok(res) t.equal(res.messageID, 123) t.equal(res.status, 0) t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.writeEnumeration(0) ber.writeString('cn=root') ber.writeString('foo') const res = new ModifyDNResponse() t.ok(res._parse(new BerReader(ber.buffer))) t.equal(res.status, 0) t.equal(res.matchedDN, 'cn=root') t.equal(res.errorMessage, 'foo') t.end() }) test('toBer', function (t) { const res = new ModifyDNResponse({ messageID: 123, status: 3, matchedDN: 'cn=root', errorMessage: 'foo' }) t.ok(res) const ber = new BerReader(res.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x6d) t.equal(ber.readEnumeration(), 3) t.equal(ber.readString(), 'cn=root') t.equal(ber.readString(), 'foo') t.end() }) node-ldapjs-2.3.3/test/messages/modify_request.test.js000066400000000000000000000042321424777304200230730ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { ModifyRequest, Attribute, Change, dn } = require('../../lib') test('new no args', function (t) { t.ok(new ModifyRequest()) t.end() }) test('new with args', function (t) { const req = new ModifyRequest({ object: dn.parse('cn=foo, o=test'), changes: [new Change({ operation: 'Replace', modification: new Attribute({ type: 'objectclass', vals: ['person'] }) })] }) t.ok(req) t.equal(req.dn.toString(), 'cn=foo, o=test') t.equal(req.changes.length, 1) t.equal(req.changes[0].operation, 'replace') t.equal(req.changes[0].modification.type, 'objectclass') t.equal(req.changes[0].modification.vals[0], 'person') t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.writeString('cn=foo, o=test') ber.startSequence() ber.startSequence() ber.writeEnumeration(0x02) ber.startSequence() ber.writeString('objectclass') ber.startSequence(0x31) ber.writeString('person') ber.endSequence() ber.endSequence() ber.endSequence() ber.endSequence() const req = new ModifyRequest() t.ok(req._parse(new BerReader(ber.buffer))) t.equal(req.dn.toString(), 'cn=foo, o=test') t.equal(req.changes.length, 1) t.equal(req.changes[0].operation, 'replace') t.equal(req.changes[0].modification.type, 'objectclass') t.equal(req.changes[0].modification.vals[0], 'person') t.end() }) test('toBer', function (t) { const req = new ModifyRequest({ messageID: 123, object: dn.parse('cn=foo, o=test'), changes: [new Change({ operation: 'Replace', modification: new Attribute({ type: 'objectclass', vals: ['person'] }) })] }) t.ok(req) const ber = new BerReader(req.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x66) t.equal(ber.readString(), 'cn=foo, o=test') t.ok(ber.readSequence()) t.ok(ber.readSequence()) t.equal(ber.readEnumeration(), 0x02) t.ok(ber.readSequence()) t.equal(ber.readString(), 'objectclass') t.equal(ber.readSequence(), 0x31) t.equal(ber.readString(), 'person') t.end() }) node-ldapjs-2.3.3/test/messages/modify_response.test.js000066400000000000000000000022471424777304200232450ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { ModifyResponse } = require('../../lib') test('new no args', function (t) { t.ok(new ModifyResponse()) t.end() }) test('new with args', function (t) { const res = new ModifyResponse({ messageID: 123, status: 0 }) t.ok(res) t.equal(res.messageID, 123) t.equal(res.status, 0) t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.writeEnumeration(0) ber.writeString('cn=root') ber.writeString('foo') const res = new ModifyResponse() t.ok(res._parse(new BerReader(ber.buffer))) t.equal(res.status, 0) t.equal(res.matchedDN, 'cn=root') t.equal(res.errorMessage, 'foo') t.end() }) test('toBer', function (t) { const res = new ModifyResponse({ messageID: 123, status: 3, matchedDN: 'cn=root', errorMessage: 'foo' }) t.ok(res) const ber = new BerReader(res.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x67) t.equal(ber.readEnumeration(), 3) t.equal(ber.readString(), 'cn=root') t.equal(ber.readString(), 'foo') t.end() }) node-ldapjs-2.3.3/test/messages/parser.test.js000066400000000000000000000020011424777304200213200ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { Parser, LDAPMessage, LDAP_REQ_EXTENSION } = require('../../lib') test('wrong protocol error', function (t) { const p = new Parser() p.once('error', function (err) { t.ok(err) t.end() }) // Send some bogus data to incur an error p.write(Buffer.from([16, 1, 4])) }) test('bad protocol op', function (t) { const p = new Parser() const message = new LDAPMessage({ protocolOp: 254 // bogus (at least today) }) p.once('error', function (err) { t.ok(err) t.ok(/not supported$/.test(err.message)) t.end() }) p.write(message.toBer()) }) test('bad message structure', function (t) { const p = new Parser() // message with bogus structure const message = new LDAPMessage({ protocolOp: LDAP_REQ_EXTENSION }) message._toBer = function (writer) { writer.writeBuffer(Buffer.from([16, 1, 4]), 80) return writer } p.once('error', function (err) { t.ok(err) t.end() }) p.write(message.toBer()) }) node-ldapjs-2.3.3/test/messages/search_entry.test.js000066400000000000000000000045101424777304200225210ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { SearchEntry, Attribute, dn } = require('../../lib') test('new no args', function (t) { t.ok(new SearchEntry()) t.end() }) test('new with args', function (t) { const res = new SearchEntry({ messageID: 123, objectName: dn.parse('cn=foo, o=test'), attributes: [new Attribute({ type: 'cn', vals: ['foo'] }), new Attribute({ type: 'objectclass', vals: ['person'] })] }) t.ok(res) t.equal(res.messageID, 123) t.equal(res.dn.toString(), 'cn=foo, o=test') t.equal(res.attributes.length, 2) t.equal(res.attributes[0].type, 'cn') t.equal(res.attributes[0].vals[0], 'foo') t.equal(res.attributes[1].type, 'objectclass') t.equal(res.attributes[1].vals[0], 'person') t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.writeString('cn=foo, o=test') ber.startSequence() ber.startSequence() ber.writeString('cn') ber.startSequence(0x31) ber.writeString('foo') ber.endSequence() ber.endSequence() ber.startSequence() ber.writeString('objectclass') ber.startSequence(0x31) ber.writeString('person') ber.endSequence() ber.endSequence() ber.endSequence() const res = new SearchEntry() t.ok(res._parse(new BerReader(ber.buffer))) t.equal(res.dn, 'cn=foo, o=test') t.equal(res.attributes.length, 2) t.equal(res.attributes[0].type, 'cn') t.equal(res.attributes[0].vals[0], 'foo') t.equal(res.attributes[1].type, 'objectclass') t.equal(res.attributes[1].vals[0], 'person') t.end() }) test('toBer', function (t) { const res = new SearchEntry({ messageID: 123, objectName: dn.parse('cn=foo, o=test'), attributes: [new Attribute({ type: 'cn', vals: ['foo'] }), new Attribute({ type: 'objectclass', vals: ['person'] })] }) t.ok(res) const ber = new BerReader(res.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x64) t.equal(ber.readString(), 'cn=foo,o=test') t.ok(ber.readSequence()) t.ok(ber.readSequence()) t.equal(ber.readString(), 'cn') t.equal(ber.readSequence(), 0x31) t.equal(ber.readString(), 'foo') t.ok(ber.readSequence()) t.equal(ber.readString(), 'objectclass') t.equal(ber.readSequence(), 0x31) t.equal(ber.readString(), 'person') t.end() }) node-ldapjs-2.3.3/test/messages/search_request.test.js000066400000000000000000000045571424777304200230630ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { SearchRequest, EqualityFilter, dn } = require('../../lib') test('new no args', function (t) { t.ok(new SearchRequest()) t.end() }) test('new with args', function (t) { const req = new SearchRequest({ baseObject: dn.parse('cn=foo, o=test'), filter: new EqualityFilter({ attribute: 'email', value: 'foo@bar.com' }), attributes: ['cn', 'sn'] }) t.ok(req) t.equal(req.dn.toString(), 'cn=foo, o=test') t.equal(req.filter.toString(), '(email=foo@bar.com)') t.equal(req.attributes.length, 2) t.equal(req.attributes[0], 'cn') t.equal(req.attributes[1], 'sn') t.end() }) test('parse', function (t) { const f = new EqualityFilter({ attribute: 'email', value: 'foo@bar.com' }) let ber = new BerWriter() ber.writeString('cn=foo, o=test') ber.writeEnumeration(0) ber.writeEnumeration(0) ber.writeInt(1) ber.writeInt(2) ber.writeBoolean(false) ber = f.toBer(ber) const req = new SearchRequest() t.ok(req._parse(new BerReader(ber.buffer))) t.equal(req.dn.toString(), 'cn=foo, o=test') t.equal(req.scope, 'base') t.equal(req.derefAliases, 0) t.equal(req.sizeLimit, 1) t.equal(req.timeLimit, 2) t.equal(req.typesOnly, false) t.equal(req.filter.toString(), '(email=foo@bar.com)') t.equal(req.attributes.length, 0) t.end() }) test('toBer', function (t) { const req = new SearchRequest({ messageID: 123, baseObject: dn.parse('cn=foo, o=test'), scope: 1, derefAliases: 2, sizeLimit: 10, timeLimit: 20, typesOnly: true, filter: new EqualityFilter({ attribute: 'email', value: 'foo@bar.com' }), attributes: ['cn', 'sn'] }) t.ok(req) const ber = new BerReader(req.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x63) // Make sure we've removed spaces from between RDNs: t.equal(ber.readString(), 'cn=foo,o=test') t.equal(ber.readEnumeration(), 1) t.equal(ber.readEnumeration(), 2) t.equal(ber.readInt(), 10) t.equal(ber.readInt(), 20) t.ok(ber.readBoolean()) t.equal(ber.readSequence(), 0xa3) t.equal(ber.readString(), 'email') t.equal(ber.readString(), 'foo@bar.com') t.ok(ber.readSequence()) t.equal(ber.readString(), 'cn') t.equal(ber.readString(), 'sn') t.end() }) node-ldapjs-2.3.3/test/messages/search_response.test.js000066400000000000000000000022471424777304200232230ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { SearchResponse } = require('../../lib') test('new no args', function (t) { t.ok(new SearchResponse()) t.end() }) test('new with args', function (t) { const res = new SearchResponse({ messageID: 123, status: 0 }) t.ok(res) t.equal(res.messageID, 123) t.equal(res.status, 0) t.end() }) test('parse', function (t) { const ber = new BerWriter() ber.writeEnumeration(0) ber.writeString('cn=root') ber.writeString('foo') const res = new SearchResponse() t.ok(res._parse(new BerReader(ber.buffer))) t.equal(res.status, 0) t.equal(res.matchedDN, 'cn=root') t.equal(res.errorMessage, 'foo') t.end() }) test('toBer', function (t) { const res = new SearchResponse({ messageID: 123, status: 3, matchedDN: 'cn=root', errorMessage: 'foo' }) t.ok(res) const ber = new BerReader(res.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.equal(ber.readSequence(), 0x65) t.equal(ber.readEnumeration(), 3) t.equal(ber.readString(), 'cn=root') t.equal(ber.readString(), 'foo') t.end() }) node-ldapjs-2.3.3/test/messages/unbind_request.test.js000066400000000000000000000013011424777304200230550ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { BerReader, BerWriter } = require('asn1') const { UnbindRequest } = require('../../lib') test('new no args', function (t) { t.ok(new UnbindRequest()) t.end() }) test('new with args', function (t) { const req = new UnbindRequest({}) t.ok(req) t.end() }) test('parse', function (t) { const ber = new BerWriter() const req = new UnbindRequest() t.ok(req._parse(new BerReader(ber.buffer))) t.end() }) test('toBer', function (t) { const req = new UnbindRequest({ messageID: 123 }) t.ok(req) const ber = new BerReader(req.toBer()) t.ok(ber) t.equal(ber.readSequence(), 0x30) t.equal(ber.readInt(), 123) t.end() }) node-ldapjs-2.3.3/test/server.test.js000066400000000000000000000314251424777304200175370ustar00rootroot00000000000000'use strict' const net = require('net') const tap = require('tap') const vasync = require('vasync') const { getSock } = require('./utils') const ldap = require('../lib') const SERVER_PORT = process.env.SERVER_PORT || 1389 const SUFFIX = 'dc=test' tap.beforeEach(function (t) { // We do not need a `.afterEach` to clean up the sock files because that // is done when the server is destroyed. t.context.sock = getSock() }) tap.test('basic create', function (t) { const server = ldap.createServer() t.ok(server) t.end() }) tap.test('connection count', function (t) { const server = ldap.createServer() t.ok(server) server.listen(0, '127.0.0.1', function () { t.ok(true, 'server listening on ' + server.url) server.getConnections(function (err, count) { t.error(err) t.equal(count, 0) const client = ldap.createClient({ url: server.url }) client.on('connect', function () { t.ok(true, 'client connected') server.getConnections(function (err, count) { t.error(err) t.equal(count, 1) client.unbind() server.close(() => t.end()) }) }) }) }) }) tap.test('properties', function (t) { const server = ldap.createServer() t.equal(server.name, 'LDAPServer') // TODO: better test server.maxConnections = 10 t.equal(server.maxConnections, 10) t.equal(server.url, null, 'url empty before bind') // listen on a random port so we have a url server.listen(0, 'localhost', function () { t.ok(server.url) server.close(() => t.end()) }) }) tap.test('IPv6 URL is formatted correctly', function (t) { const server = ldap.createServer() t.equal(server.url, null, 'url empty before bind') server.listen(0, '::1', function () { t.ok(server.url) t.equal(server.url, 'ldap://[::1]:' + server.port) server.close(() => t.end()) }) }) tap.test('listen on unix/named socket', function (t) { const server = ldap.createServer() server.listen(t.context.sock, function () { t.ok(server.url) t.equal(server.url.split(':')[0], 'ldapi') server.close(() => t.end()) }) }) tap.test('listen on static port', function (t) { const server = ldap.createServer() server.listen(SERVER_PORT, '127.0.0.1', function () { const addr = server.address() t.equal(addr.port, parseInt(SERVER_PORT, 10)) t.equal(server.url, `ldap://127.0.0.1:${SERVER_PORT}`) server.close(() => t.end()) }) }) tap.test('listen on ephemeral port', function (t) { const server = ldap.createServer() server.listen(0, 'localhost', function () { const addr = server.address() t.ok(addr.port > 0) t.ok(addr.port < 65535) server.close(() => t.end()) }) }) tap.test('route order', function (t) { function generateHandler (response) { const func = function handler (req, res, next) { res.send({ dn: response, attributes: { } }) res.end() return next() } return func } const server = ldap.createServer() const sock = t.context.sock const dnShort = SUFFIX const dnMed = 'dc=sub,' + SUFFIX const dnLong = 'dc=long,dc=sub,' + SUFFIX // Mount routes out of order server.search(dnMed, generateHandler(dnMed)) server.search(dnShort, generateHandler(dnShort)) server.search(dnLong, generateHandler(dnLong)) server.listen(sock, function () { t.ok(true, 'server listen') const client = ldap.createClient({ socketPath: sock }) client.on('connect', () => { vasync.forEachParallel({ func: runSearch, inputs: [dnShort, dnMed, dnLong] }, function (err) { t.error(err) client.unbind() server.close(() => t.end()) }) }) function runSearch (value, cb) { client.search(value, '(objectclass=*)', function (err, res) { t.error(err) t.ok(res) res.on('searchEntry', function (entry) { t.equal(entry.dn.toString(), value) }) res.on('end', function () { cb() }) }) } }) }) tap.test('route absent', function (t) { const server = ldap.createServer() const DN_ROUTE = 'dc=base' const DN_MISSING = 'dc=absent' server.bind(DN_ROUTE, function (req, res, next) { res.end() return next() }) server.listen(t.context.sock, function () { t.ok(true, 'server startup') vasync.parallel({ funcs: [ function presentBind (cb) { const clt = ldap.createClient({ socketPath: t.context.sock }) clt.bind(DN_ROUTE, '', function (err) { t.notOk(err) clt.unbind() cb() }) }, function absentBind (cb) { const clt = ldap.createClient({ socketPath: t.context.sock }) clt.bind(DN_MISSING, '', function (err) { t.ok(err) t.equal(err.code, ldap.LDAP_NO_SUCH_OBJECT) clt.unbind() cb() }) } ] }, function (err) { t.notOk(err) server.close(() => t.end()) }) }) }) tap.test('route unbind', function (t) { const server = ldap.createServer() server.unbind(function (req, res, next) { t.ok(true, 'server unbind successful') res.end() return next() }) server.listen(t.context.sock, function () { t.ok(true, 'server startup') const client = ldap.createClient({ socketPath: t.context.sock }) client.bind('', '', function (err) { t.error(err, 'client bind error') client.unbind(function (err) { t.error(err, 'client unbind error') server.close(() => t.end()) }) }) }) }) tap.test('bind/unbind identity anonymous', function (t) { const server = ldap.createServer({ connectionRouter: function (c) { server.newConnection(c) server.emit('testconnection', c) } }) server.unbind(function (req, res, next) { t.ok(true, 'server unbind successful') res.end() return next() }) server.bind('', function (req, res, next) { t.ok(true, 'server bind successful') res.end() return next() }) const anonDN = ldap.dn.parse('cn=anonymous') server.listen(t.context.sock, function () { t.ok(true, 'server startup') const client = ldap.createClient({ socketPath: t.context.sock }) server.once('testconnection', (c) => { t.ok(anonDN.equals(c.ldap.bindDN), 'pre bind dn is correct') client.bind('', '', function (err) { t.error(err, 'client anon bind error') t.ok(anonDN.equals(c.ldap.bindDN), 'anon bind dn is correct') client.unbind(function (err) { t.error(err, 'client anon unbind error') t.ok(anonDN.equals(c.ldap.bindDN), 'anon unbind dn is correct') server.close(() => t.end()) }) }) }) }) }) tap.test('bind/unbind identity user', function (t) { const server = ldap.createServer({ connectionRouter: function (c) { server.newConnection(c) server.emit('testconnection', c) } }) server.unbind(function (req, res, next) { t.ok(true, 'server unbind successful') res.end() return next() }) server.bind('', function (req, res, next) { t.ok(true, 'server bind successful') res.end() return next() }) const anonDN = ldap.dn.parse('cn=anonymous') const testDN = ldap.dn.parse('cn=anotheruser') server.listen(t.context.sock, function () { t.ok(true, 'server startup') const client = ldap.createClient({ socketPath: t.context.sock }) server.once('testconnection', (c) => { t.ok(anonDN.equals(c.ldap.bindDN), 'pre bind dn is correct') client.bind(testDN.toString(), 'somesecret', function (err) { t.error(err, 'user bind error') t.ok(testDN.equals(c.ldap.bindDN), 'user bind dn is correct') // check rebinds too client.bind('', '', function (err) { t.error(err, 'client anon bind error') t.ok(anonDN.equals(c.ldap.bindDN), 'anon bind dn is correct') // user rebind client.bind(testDN.toString(), 'somesecret', function (err) { t.error(err, 'user bind error') t.ok(testDN.equals(c.ldap.bindDN), 'user rebind dn is correct') client.unbind(function (err) { t.error(err, 'user unbind error') t.ok(anonDN.equals(c.ldap.bindDN), 'user unbind dn is correct') server.close(() => t.end()) }) }) }) }) }) }) }) tap.test('strict routing', function (t) { const testDN = 'cn=valid' let clt let server const sock = t.context.sock vasync.pipeline({ funcs: [ function setup (_, cb) { server = ldap.createServer({ // strictDN: true - on by default }) // invalid DNs would go to default handler server.search('', function (req, res, next) { t.ok(req.dn) t.equal(typeof (req.dn), 'object') t.equal(req.dn.toString(), testDN) res.end() next() }) server.listen(sock, function () { t.ok(true, 'server startup') clt = ldap.createClient({ socketPath: sock, strictDN: false }) cb() }) }, function testBad (_, cb) { clt.search('not a dn', { scope: 'base' }, function (err, res) { t.error(err) res.once('error', function (err2) { t.ok(err2) t.equal(err2.code, ldap.LDAP_INVALID_DN_SYNTAX) cb() }) res.once('end', function () { t.fail('accepted invalid dn') cb(Error('bogus')) }) }) }, function testGood (_, cb) { clt.search(testDN, { scope: 'base' }, function (err, res) { t.error(err) res.once('error', function (err2) { t.error(err2) cb(err2) }) res.once('end', function (result) { t.ok(result, 'accepted invalid dn') cb() }) }) } ] }, function (err) { t.error(err) if (clt) { clt.destroy() } server.close(() => t.end()) }) }) tap.test('non-strict routing', function (t) { const server = ldap.createServer({ strictDN: false }) const testDN = 'this ain\'t a DN' // invalid DNs go to default handler server.search('', function (req, res, next) { t.ok(req.dn) t.equal(typeof (req.dn), 'string') t.equal(req.dn, testDN) res.end() next() }) server.listen(t.context.sock, function () { t.ok(true, 'server startup') const clt = ldap.createClient({ socketPath: t.context.sock, strictDN: false }) clt.search(testDN, { scope: 'base' }, function (err, res) { t.error(err) res.on('end', function () { clt.destroy() server.close(() => t.end()) }) }) }) }) tap.test('close accept a callback', function (t) { const server = ldap.createServer() // callback is called when the server is closed server.listen(0, function (err) { t.error(err) server.close(function (err) { t.error(err) t.end() }) }) }) tap.test('close without error calls callback', function (t) { const server = ldap.createServer() // when the server is closed without error, the callback parameter is undefined server.listen(1389, '127.0.0.1', function (err) { t.error(err) server.close(function (err) { t.error(err) t.end() }) }) }) tap.test('close passes error to callback', function (t) { const server = ldap.createServer() // when the server is closed with an error, the error is the first parameter of the callback server.close(function (err) { t.ok(err) t.end() }) }) tap.test('multithreading support via external server', function (t) { const serverOptions = { } const server = ldap.createServer(serverOptions) const fauxServer = net.createServer(serverOptions, (connection) => { server.newConnection(connection) }) fauxServer.log = serverOptions.log fauxServer.ldap = { config: serverOptions } t.ok(server) fauxServer.listen(5555, '127.0.0.1', function () { t.ok(true, 'server listening on ' + server.url) t.ok(fauxServer) const client = ldap.createClient({ url: 'ldap://127.0.0.1:5555' }) client.on('connect', function () { t.ok(client) client.unbind() fauxServer.close(() => t.end()) }) }) }) tap.test('multithreading support via hook', function (t) { const serverOptions = { connectionRouter: (connection) => { server.newConnection(connection) } } const server = ldap.createServer(serverOptions) const fauxServer = ldap.createServer(serverOptions) t.ok(server) fauxServer.listen(0, '127.0.0.1', function () { t.ok(true, 'server listening on ' + server.url) t.ok(fauxServer) const client = ldap.createClient({ url: fauxServer.url }) client.on('connect', function () { t.ok(client) client.unbind() fauxServer.close(() => t.end()) }) }) }) node-ldapjs-2.3.3/test/url.test.js000066400000000000000000000027341424777304200170340ustar00rootroot00000000000000'use strict' const { test } = require('tap') const { parseURL } = require('../lib') test('parse empty', function (t) { const u = parseURL('ldap:///') t.equal(u.hostname, 'localhost') t.equal(u.port, 389) t.ok(!u.DN) t.ok(!u.attributes) t.equal(u.secure, false) t.end() }) test('parse hostname', function (t) { const u = parseURL('ldap://example.com/') t.equal(u.hostname, 'example.com') t.equal(u.port, 389) t.ok(!u.DN) t.ok(!u.attributes) t.equal(u.secure, false) t.end() }) test('parse host and port', function (t) { const u = parseURL('ldap://example.com:1389/') t.equal(u.hostname, 'example.com') t.equal(u.port, 1389) t.ok(!u.DN) t.ok(!u.attributes) t.equal(u.secure, false) t.end() }) test('parse full', function (t) { const u = parseURL('ldaps://ldap.example.com:1389/dc=example%20,dc=com' + '?cn,sn?sub?(cn=Babs%20Jensen)') t.equal(u.secure, true) t.equal(u.hostname, 'ldap.example.com') t.equal(u.port, 1389) t.equal(u.DN, 'dc=example ,dc=com') t.ok(u.attributes) t.equal(u.attributes.length, 2) t.equal(u.attributes[0], 'cn') t.equal(u.attributes[1], 'sn') t.equal(u.scope, 'sub') t.equal(u.filter.toString(), '(cn=Babs Jensen)') t.end() }) test('supports href', function (t) { const u = parseURL('ldaps://ldap.example.com:1389/dc=example%20,dc=com?cn,sn?sub?(cn=Babs%20Jensen)') t.equal(u.href, 'ldaps://ldap.example.com:1389/dc=example%20,dc=com?cn,sn?sub?(cn=Babs%20Jensen)') t.end() }) node-ldapjs-2.3.3/test/utils.js000066400000000000000000000005571424777304200164150ustar00rootroot00000000000000'use strict' const os = require('os') const path = require('path') const crypto = require('crypto') function uuid () { return crypto.randomBytes(16).toString('hex') } function getSock () { if (process.platform === 'win32') { return '\\\\.\\pipe\\' + uuid() } else { return path.join(os.tmpdir(), uuid()) } } module.exports = { getSock, uuid }