ldap-authentication 3.2.4 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,5 +1,42 @@
1
1
  const assert = require('assert')
2
- const ldap = require('ldapjs')
2
+ const ldapts = require('ldapts')
3
+ // escape the , in CN in DN
4
+ function _ldapEscapeDN(s) {
5
+ let ret = "";
6
+ let comaPositions = [];
7
+ let done = false;
8
+ let countEq = 0;
9
+ for (let i = 0; !done && i < s.length; i++) {
10
+ switch (s[i]) {
11
+ case "\\":
12
+ // user already escapped, continue
13
+ i++;
14
+ break;
15
+ case ",":
16
+ if (countEq == 1) {
17
+ comaPositions.push(i);
18
+ }
19
+ break;
20
+ case "=":
21
+ countEq++;
22
+ if (countEq == 2) {
23
+ done = true;
24
+ }
25
+ break;
26
+ }
27
+ }
28
+ if (done) {
29
+ comaPositions.pop();
30
+ }
31
+ let lastIndex = 0;
32
+ for (let i of comaPositions) {
33
+ ret += s.substring(lastIndex, i);
34
+ ret += "\\,";
35
+ lastIndex = i + 1;
36
+ }
37
+ ret += s.substring(lastIndex);
38
+ return ret;
39
+ }
3
40
 
4
41
  // convert an escaped utf8 string returned from ldapjs
5
42
  function _isHex(c) {
@@ -41,6 +78,8 @@ function _recursiveParseHexString(obj) {
41
78
  }
42
79
  return obj
43
80
  }
81
+ /*
82
+ // UPDATE: This function is not used in this version, because ldapts library already does the conversion
44
83
  // convert a SearchResultEntry object in ldapjs 3.0
45
84
  // to a user object to maintain backward compatibility
46
85
 
@@ -53,58 +92,21 @@ function _searchResultToUser(pojo) {
53
92
  })
54
93
  return _recursiveParseHexString(user)
55
94
  }
95
+ */
56
96
  // bind and return the ldap client
57
- function _ldapBind(dn, password, starttls, ldapOpts) {
58
- return new Promise(function (resolve, reject) {
59
- ldapOpts.connectTimeout = ldapOpts.connectTimeout || 5000
60
- let client = ldap.createClient(ldapOpts)
97
+ async function _ldapBind(dn, password, starttls, ldapOpts) {
98
+ // TODO: check if ldapts expects escaped dn or not (possible double escaping problems?)
99
+ dn = _ldapEscapeDN(dn)
100
+ ldapOpts.connectTimeout = ldapOpts.connectTimeout || 5000
101
+ let client = new ldapts.Client(ldapOpts)
61
102
 
62
- client.on('connect', function () {
63
- if (starttls) {
64
- client.starttls(ldapOpts.tlsOptions, null, function (error) {
65
- if (error) {
66
- reject(error)
67
- return
68
- }
69
- client.bind(dn, password, function (err) {
70
- if (err) {
71
- reject(err)
72
- return
73
- }
74
- ldapOpts.log && ldapOpts.log.trace('bind success!')
75
- resolve(client)
76
- })
77
- })
78
- } else {
79
- client.bind(dn, password, function (err) {
80
- if (err) {
81
- reject(err)
82
- return
83
- }
84
- ldapOpts.log && ldapOpts.log.trace('bind success!')
85
- resolve(client)
86
- })
87
- }
88
- })
89
-
90
- //Fix for issue https://github.com/shaozi/ldap-authentication/issues/13
91
- client.on('timeout', (err) => {
92
- reject(err)
93
- })
94
- client.on('connectTimeout', (err) => {
95
- reject(err)
96
- })
97
- client.on('error', (err) => {
98
- reject(err)
99
- })
103
+ if(starttls) {
104
+ await client.startTLS(ldapOpts.tlsOptions)
105
+ }
100
106
 
101
- client.on('connectError', function (error) {
102
- if (error) {
103
- reject(error)
104
- return
105
- }
106
- })
107
- })
107
+ await client.bind(dn, password)
108
+ ldapOpts.log && ldapOpts.log.trace('bind success!')
109
+ return client
108
110
  }
109
111
 
110
112
  // search a user and return the object
@@ -115,48 +117,36 @@ async function _searchUser(
115
117
  username,
116
118
  attributes = null
117
119
  ) {
118
- return new Promise(function (resolve, reject) {
119
- let filter = new ldap.filters.EqualityFilter({
120
- attribute: usernameAttribute,
121
- value: username,
122
- })
123
- let searchOptions = {
124
- filter: filter,
125
- scope: 'sub',
126
- attributes: attributes,
127
- }
128
- if (attributes) {
129
- searchOptions.attributes = attributes
130
- }
131
- ldapClient.search(searchBase, searchOptions, function (err, res) {
132
- let user = null
133
- if (err) {
134
- reject(err)
135
- return
136
- }
137
- res.on('searchEntry', function (entry) {
138
- user = _searchResultToUser(entry.pojo)
139
- })
140
- res.on('searchReference', function (referral) {
141
- // TODO: we don't support reference yet
142
- // If the server was able to locate the entry referred to by the baseObject
143
- // but could not search one or more non-local entries,
144
- // the server may return one or more SearchResultReference messages,
145
- // each containing a reference to another set of servers for continuing the operation.
146
- // referral.uris
147
- })
148
- res.on('error', function (err) {
149
- reject(err)
150
- })
151
- res.on('end', function (result) {
152
- if (result.status != 0) {
153
- reject(new Error('ldap search status is not 0, search failed'))
154
- } else {
155
- resolve(user)
156
- }
157
- })
158
- })
159
- })
120
+
121
+ let filter = new ldapts.EqualityFilter({
122
+ attribute: usernameAttribute,
123
+ value: username,
124
+ });
125
+ let searchOptions = {
126
+ filter: filter,
127
+ scope: 'sub',
128
+ attributes: attributes,
129
+ };
130
+ if (attributes) {
131
+ searchOptions.attributes = attributes;
132
+ }
133
+
134
+ // TODO: we don't support reference yet
135
+ // If the server was able to locate the entry referred to by the baseObject
136
+ // but could not search one or more non-local entries,
137
+ // the server may return one or more SearchResultReference messages,
138
+ // each containing a reference to another set of servers for continuing the operation.
139
+ // referral.uris
140
+ const { searchEntries, searchReferences } = await ldapClient.search(searchBase, searchOptions);
141
+
142
+ let user;
143
+ if(!searchEntries || searchEntries.length < 1 || !searchEntries[0] || !searchEntries[0].dn) {
144
+ user = null;
145
+ } else {
146
+ user = searchEntries[0];
147
+ }
148
+
149
+ return user;
160
150
  }
161
151
 
162
152
  // search a groups which user is member
@@ -168,36 +158,37 @@ async function _searchUserGroups(
168
158
  groupMemberAttribute = 'member',
169
159
  groupMemberUserAttribute = 'dn'
170
160
  ) {
171
- return new Promise(function (resolve, reject) {
172
- ldapClient.search(
173
- searchBase,
174
- {
175
- filter: `(&(objectclass=${groupClass})(${groupMemberAttribute}=${user[groupMemberUserAttribute]}))`,
176
- scope: 'sub',
177
- },
178
- function (err, res) {
179
- let groups = []
180
- if (err) {
181
- reject(err)
182
- return
183
- }
184
- res.on('searchEntry', function (entry) {
185
- groups.push(_recursiveParseHexString(entry.pojo))
186
- })
187
- res.on('searchReference', function (referral) {})
188
- res.on('error', function (err) {
189
- reject(err)
190
- })
191
- res.on('end', function (result) {
192
- if (result.status != 0) {
193
- reject(new Error('ldap search status is not 0, search failed'))
194
- } else {
195
- resolve(groups)
196
- }
197
- })
198
- }
199
- )
200
- })
161
+ // Below works, but prefer using ldapts Filter subclasses to build this search, so that correct escaping is done
162
+ // const filter = `(&(objectclass=${groupClass})(${groupMemberAttribute}=${user[groupMemberUserAttribute]}))`
163
+ const filter = new ldapts.AndFilter({
164
+ filters: [
165
+ new ldapts.EqualityFilter({ attribute: 'objectclass', value: groupClass }),
166
+ new ldapts.EqualityFilter({ attribute: groupMemberAttribute, value: user[groupMemberUserAttribute] }),
167
+ ],
168
+ });
169
+
170
+ const { searchEntries, searchReferences } = await ldapClient.search(
171
+ searchBase,
172
+ {
173
+ filter: filter,
174
+ scope: 'sub',
175
+ }
176
+ );
177
+
178
+ let groups;
179
+ if(!searchEntries || searchEntries.length < 1) {
180
+ groups = [];
181
+ } else {
182
+ groups = searchEntries;
183
+ }
184
+ // ldapjs has group.objectName, ldapts does not have it. instead, use dn
185
+ // add objectName back for backward compatibility
186
+ for (let group of groups) {
187
+ if (typeof group.objectName === 'undefined') {
188
+ group.objectName = group.dn
189
+ }
190
+ }
191
+ return groups;
201
192
  }
202
193
 
203
194
  async function authenticateWithAdmin(
@@ -468,4 +459,5 @@ module.exports.exportForTesting = {
468
459
  _isHex,
469
460
  _parseEscapedHexToUtf8,
470
461
  _recursiveParseHexString,
462
+ _ldapEscapeDN
471
463
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ldap-authentication",
3
- "version": "3.2.4",
3
+ "version": "3.3.1",
4
4
  "description": "A simple async nodejs library for LDAP user authentication",
5
5
  "main": "index.js",
6
6
  "types": "./index.d.ts",
@@ -36,9 +36,9 @@
36
36
  },
37
37
  "homepage": "https://github.com/shaozi/ldap-authentication#readme",
38
38
  "dependencies": {
39
- "ldapjs": "^3.0.7"
39
+ "ldapts": "^7.3.1"
40
40
  },
41
41
  "devDependencies": {
42
- "jasmine": "^5.3.0"
42
+ "jasmine": "^5.6.0"
43
43
  }
44
44
  }
@@ -1,5 +1,5 @@
1
1
  const { exportForTesting } = require('../index.js')
2
- const { _isHex, _parseEscapedHexToUtf8, _recursiveParseHexString } =
2
+ const { _isHex, _parseEscapedHexToUtf8, _recursiveParseHexString, _ldapEscapeDN } =
3
3
  exportForTesting
4
4
 
5
5
  describe('string conversion test', () => {
@@ -41,3 +41,22 @@ describe('string conversion test', () => {
41
41
  expect(converted).toEqual(target)
42
42
  })
43
43
  })
44
+
45
+ describe('escape , in the DN test', ()=>{
46
+ let cases = [
47
+ { s: "a", want: "a" },
48
+ { s: "", want: "" },
49
+ { s: "CN=a,DN=b", want: "CN=a,DN=b" },
50
+ { s: "CN=a, c,DN=b", want: "CN=a\\, c,DN=b" },
51
+ { s: "CN=a\\, c,DN=b", want: "CN=a\\, c,DN=b" },
52
+ { s: "CN=a, b, c,DN=b", want: "CN=a\\, b\\, c,DN=b" },
53
+ { s: "CN=a, c", want: "CN=a\\, c" },
54
+ ]
55
+ for (let c of cases) {
56
+ it("escape "+c.s, ()=>{
57
+ let got = _ldapEscapeDN(c.s)
58
+ expect(got).toEqual(c.want)
59
+ })
60
+
61
+ }
62
+ })
package/test/test.spec.js CHANGED
@@ -144,9 +144,7 @@ describe('ldap-authentication test', () => {
144
144
  let user = await authenticate(options)
145
145
  expect(user).toBeTruthy()
146
146
  expect(user.groups.length).toBeGreaterThan(0)
147
- expect(user.groups[0].objectName).toEqual(
148
- 'cn=科学A部,ou=users,dc=example,dc=com'
149
- )
147
+ expect(user.groups[0].dn).toEqual('cn=科学A部,ou=users,dc=example,dc=com')
150
148
  })
151
149
  it('Use regular user to authenticate and fetch user group information', async () => {
152
150
  let options = {
@@ -167,6 +165,8 @@ describe('ldap-authentication test', () => {
167
165
  let user = await authenticate(options)
168
166
  expect(user).toBeTruthy()
169
167
  expect(user.groups.length).toBeGreaterThan(0)
168
+ expect(user.groups[0].dn).toEqual('cn=科学A部,ou=users,dc=example,dc=com')
169
+ // backward compatible with 3.2
170
170
  expect(user.groups[0].objectName).toEqual(
171
171
  'cn=科学A部,ou=users,dc=example,dc=com'
172
172
  )