ldap-authentication 3.1.0 → 3.2.2
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/.devcontainer/docker-compose.yml +1 -0
- package/.github/workflows/integration-test.yml +1 -1
- package/.vscode/launch.json +7 -0
- package/README.md +1 -1
- package/docker-compose.yml +1 -0
- package/index.js +61 -56
- package/package.json +1 -1
- package/test/jasmine.js +1 -1
- package/test/string.spec.js +43 -0
- package/test/{test.js → test.spec.js} +6 -0
package/.vscode/launch.json
CHANGED
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
"name": "Debug Example",
|
|
11
11
|
"skipFiles": ["<node_internals>/**"],
|
|
12
12
|
"program": "${workspaceFolder}/example/index.js"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"type": "node",
|
|
16
|
+
"request": "launch",
|
|
17
|
+
"name": "Debug Test",
|
|
18
|
+
"skipFiles": ["<node_internals>/**"],
|
|
19
|
+
"program": "${workspaceFolder}/test/jasmine.js"
|
|
13
20
|
}
|
|
14
21
|
]
|
|
15
22
|
}
|
package/README.md
CHANGED
package/docker-compose.yml
CHANGED
package/index.js
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
const assert = require('assert')
|
|
2
2
|
const ldap = require('ldapjs')
|
|
3
3
|
|
|
4
|
+
// convert an escaped utf8 string returned from ldapjs
|
|
5
|
+
function _isHex(c) {
|
|
6
|
+
return (
|
|
7
|
+
(c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
|
|
8
|
+
)
|
|
9
|
+
}
|
|
10
|
+
function _parseEscapedHexToUtf8(s) {
|
|
11
|
+
// convert 'cn=\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8,ou=users,dc=example,dc=com'
|
|
12
|
+
// to 'cn=研发A部,ou=users,dc=example,dc=com'
|
|
13
|
+
let ret = Buffer.alloc(0)
|
|
14
|
+
let len = s.length
|
|
15
|
+
for (let i = 0; i < len; i++) {
|
|
16
|
+
let c = s[i]
|
|
17
|
+
let item
|
|
18
|
+
if (c == '\\' && i < len - 2 && _isHex(s[i + 1]) && _isHex(s[i + 2])) {
|
|
19
|
+
item = Buffer.from(s.substring(i + 1, i + 3), 'hex')
|
|
20
|
+
i += 2
|
|
21
|
+
} else {
|
|
22
|
+
item = Buffer.from(c)
|
|
23
|
+
}
|
|
24
|
+
ret = Buffer.concat([ret, item])
|
|
25
|
+
}
|
|
26
|
+
return ret.toString()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function _recursiveParseHexString(obj) {
|
|
30
|
+
if (Array.isArray(obj)) {
|
|
31
|
+
return obj.map((ele) => _recursiveParseHexString(ele))
|
|
32
|
+
}
|
|
33
|
+
if (typeof obj == 'string') {
|
|
34
|
+
return _parseEscapedHexToUtf8(obj)
|
|
35
|
+
}
|
|
36
|
+
if (typeof obj == 'object') {
|
|
37
|
+
for (let key in obj) {
|
|
38
|
+
obj[key] = _recursiveParseHexString(obj[key])
|
|
39
|
+
}
|
|
40
|
+
return obj
|
|
41
|
+
}
|
|
42
|
+
return obj
|
|
43
|
+
}
|
|
4
44
|
// convert a SearchResultEntry object in ldapjs 3.0
|
|
5
45
|
// to a user object to maintain backward compatibility
|
|
6
46
|
|
|
@@ -11,13 +51,13 @@ function _searchResultToUser(pojo) {
|
|
|
11
51
|
user[attribute.type] =
|
|
12
52
|
attribute.values.length == 1 ? attribute.values[0] : attribute.values
|
|
13
53
|
})
|
|
14
|
-
return user
|
|
54
|
+
return _recursiveParseHexString(user)
|
|
15
55
|
}
|
|
16
56
|
// bind and return the ldap client
|
|
17
57
|
function _ldapBind(dn, password, starttls, ldapOpts) {
|
|
18
58
|
return new Promise(function (resolve, reject) {
|
|
19
59
|
ldapOpts.connectTimeout = ldapOpts.connectTimeout || 5000
|
|
20
|
-
|
|
60
|
+
let client = ldap.createClient(ldapOpts)
|
|
21
61
|
|
|
22
62
|
client.on('connect', function () {
|
|
23
63
|
if (starttls) {
|
|
@@ -29,7 +69,6 @@ function _ldapBind(dn, password, starttls, ldapOpts) {
|
|
|
29
69
|
client.bind(dn, password, function (err) {
|
|
30
70
|
if (err) {
|
|
31
71
|
reject(err)
|
|
32
|
-
client.unbind()
|
|
33
72
|
return
|
|
34
73
|
}
|
|
35
74
|
ldapOpts.log && ldapOpts.log.trace('bind success!')
|
|
@@ -40,7 +79,6 @@ function _ldapBind(dn, password, starttls, ldapOpts) {
|
|
|
40
79
|
client.bind(dn, password, function (err) {
|
|
41
80
|
if (err) {
|
|
42
81
|
reject(err)
|
|
43
|
-
client.unbind()
|
|
44
82
|
return
|
|
45
83
|
}
|
|
46
84
|
ldapOpts.log && ldapOpts.log.trace('bind success!')
|
|
@@ -78,7 +116,7 @@ async function _searchUser(
|
|
|
78
116
|
attributes = null
|
|
79
117
|
) {
|
|
80
118
|
return new Promise(function (resolve, reject) {
|
|
81
|
-
|
|
119
|
+
let filter = new ldap.filters.EqualityFilter({
|
|
82
120
|
attribute: usernameAttribute,
|
|
83
121
|
value: username,
|
|
84
122
|
})
|
|
@@ -91,10 +129,9 @@ async function _searchUser(
|
|
|
91
129
|
searchOptions.attributes = attributes
|
|
92
130
|
}
|
|
93
131
|
ldapClient.search(searchBase, searchOptions, function (err, res) {
|
|
94
|
-
|
|
132
|
+
let user = null
|
|
95
133
|
if (err) {
|
|
96
134
|
reject(err)
|
|
97
|
-
ldapClient.unbind()
|
|
98
135
|
return
|
|
99
136
|
}
|
|
100
137
|
res.on('searchEntry', function (entry) {
|
|
@@ -110,7 +147,6 @@ async function _searchUser(
|
|
|
110
147
|
})
|
|
111
148
|
res.on('error', function (err) {
|
|
112
149
|
reject(err)
|
|
113
|
-
ldapClient.unbind()
|
|
114
150
|
})
|
|
115
151
|
res.on('end', function (result) {
|
|
116
152
|
if (result.status != 0) {
|
|
@@ -118,7 +154,6 @@ async function _searchUser(
|
|
|
118
154
|
} else {
|
|
119
155
|
resolve(user)
|
|
120
156
|
}
|
|
121
|
-
ldapClient.unbind()
|
|
122
157
|
})
|
|
123
158
|
})
|
|
124
159
|
})
|
|
@@ -141,19 +176,17 @@ async function _searchUserGroups(
|
|
|
141
176
|
scope: 'sub',
|
|
142
177
|
},
|
|
143
178
|
function (err, res) {
|
|
144
|
-
|
|
179
|
+
let groups = []
|
|
145
180
|
if (err) {
|
|
146
181
|
reject(err)
|
|
147
|
-
ldapClient.unbind()
|
|
148
182
|
return
|
|
149
183
|
}
|
|
150
184
|
res.on('searchEntry', function (entry) {
|
|
151
|
-
groups.push(entry.pojo)
|
|
185
|
+
groups.push(_recursiveParseHexString(entry.pojo))
|
|
152
186
|
})
|
|
153
187
|
res.on('searchReference', function (referral) {})
|
|
154
188
|
res.on('error', function (err) {
|
|
155
189
|
reject(err)
|
|
156
|
-
ldapClient.unbind()
|
|
157
190
|
})
|
|
158
191
|
res.on('end', function (result) {
|
|
159
192
|
if (result.status != 0) {
|
|
@@ -161,7 +194,6 @@ async function _searchUserGroups(
|
|
|
161
194
|
} else {
|
|
162
195
|
resolve(groups)
|
|
163
196
|
}
|
|
164
|
-
ldapClient.unbind()
|
|
165
197
|
})
|
|
166
198
|
}
|
|
167
199
|
)
|
|
@@ -183,7 +215,7 @@ async function authenticateWithAdmin(
|
|
|
183
215
|
groupMemberUserAttribute = 'dn',
|
|
184
216
|
attributes = null
|
|
185
217
|
) {
|
|
186
|
-
|
|
218
|
+
let ldapAdminClient
|
|
187
219
|
try {
|
|
188
220
|
ldapAdminClient = await _ldapBind(
|
|
189
221
|
adminDn,
|
|
@@ -194,14 +226,13 @@ async function authenticateWithAdmin(
|
|
|
194
226
|
} catch (error) {
|
|
195
227
|
throw { admin: error }
|
|
196
228
|
}
|
|
197
|
-
|
|
229
|
+
let user = await _searchUser(
|
|
198
230
|
ldapAdminClient,
|
|
199
231
|
userSearchBase,
|
|
200
232
|
usernameAttribute,
|
|
201
233
|
username,
|
|
202
234
|
attributes
|
|
203
235
|
)
|
|
204
|
-
ldapAdminClient.unbind()
|
|
205
236
|
if (!user || !user.dn) {
|
|
206
237
|
ldapOpts.log &&
|
|
207
238
|
ldapOpts.log.trace(
|
|
@@ -211,26 +242,15 @@ async function authenticateWithAdmin(
|
|
|
211
242
|
'user not found or usernameAttribute is wrong'
|
|
212
243
|
)
|
|
213
244
|
}
|
|
214
|
-
|
|
245
|
+
let userDn = user.dn
|
|
215
246
|
let ldapUserClient
|
|
216
247
|
try {
|
|
217
248
|
ldapUserClient = await _ldapBind(userDn, userPassword, starttls, ldapOpts)
|
|
218
249
|
} catch (error) {
|
|
219
250
|
throw error
|
|
220
251
|
}
|
|
221
|
-
ldapUserClient.unbind()
|
|
222
252
|
if (groupsSearchBase && groupClass && groupMemberAttribute) {
|
|
223
|
-
|
|
224
|
-
ldapAdminClient = await _ldapBind(
|
|
225
|
-
adminDn,
|
|
226
|
-
adminPassword,
|
|
227
|
-
starttls,
|
|
228
|
-
ldapOpts
|
|
229
|
-
)
|
|
230
|
-
} catch (error) {
|
|
231
|
-
throw error
|
|
232
|
-
}
|
|
233
|
-
var groups = await _searchUserGroups(
|
|
253
|
+
let groups = await _searchUserGroups(
|
|
234
254
|
ldapAdminClient,
|
|
235
255
|
groupsSearchBase,
|
|
236
256
|
user,
|
|
@@ -239,7 +259,6 @@ async function authenticateWithAdmin(
|
|
|
239
259
|
groupMemberUserAttribute
|
|
240
260
|
)
|
|
241
261
|
user.groups = groups
|
|
242
|
-
ldapAdminClient.unbind()
|
|
243
262
|
}
|
|
244
263
|
return user
|
|
245
264
|
}
|
|
@@ -266,10 +285,9 @@ async function authenticateWithUser(
|
|
|
266
285
|
}
|
|
267
286
|
if (!usernameAttribute || !userSearchBase) {
|
|
268
287
|
// if usernameAttribute is not provided, no user detail is needed.
|
|
269
|
-
ldapUserClient.unbind()
|
|
270
288
|
return true
|
|
271
289
|
}
|
|
272
|
-
|
|
290
|
+
let user = await _searchUser(
|
|
273
291
|
ldapUserClient,
|
|
274
292
|
userSearchBase,
|
|
275
293
|
usernameAttribute,
|
|
@@ -285,14 +303,8 @@ async function authenticateWithUser(
|
|
|
285
303
|
'user logged in, but user details could not be found. Probabaly usernameAttribute or userSearchBase is wrong?'
|
|
286
304
|
)
|
|
287
305
|
}
|
|
288
|
-
ldapUserClient.unbind()
|
|
289
306
|
if (groupsSearchBase && groupClass && groupMemberAttribute) {
|
|
290
|
-
|
|
291
|
-
ldapUserClient = await _ldapBind(userDn, userPassword, starttls, ldapOpts)
|
|
292
|
-
} catch (error) {
|
|
293
|
-
throw error
|
|
294
|
-
}
|
|
295
|
-
var groups = await _searchUserGroups(
|
|
307
|
+
let groups = await _searchUserGroups(
|
|
296
308
|
ldapUserClient,
|
|
297
309
|
groupsSearchBase,
|
|
298
310
|
user,
|
|
@@ -301,7 +313,6 @@ async function authenticateWithUser(
|
|
|
301
313
|
groupMemberUserAttribute
|
|
302
314
|
)
|
|
303
315
|
user.groups = groups
|
|
304
|
-
ldapUserClient.unbind()
|
|
305
316
|
}
|
|
306
317
|
return user
|
|
307
318
|
}
|
|
@@ -320,7 +331,7 @@ async function verifyUserExists(
|
|
|
320
331
|
groupMemberUserAttribute = 'dn',
|
|
321
332
|
attributes = null
|
|
322
333
|
) {
|
|
323
|
-
|
|
334
|
+
let ldapAdminClient
|
|
324
335
|
try {
|
|
325
336
|
ldapAdminClient = await _ldapBind(
|
|
326
337
|
adminDn,
|
|
@@ -331,14 +342,13 @@ async function verifyUserExists(
|
|
|
331
342
|
} catch (error) {
|
|
332
343
|
throw { admin: error }
|
|
333
344
|
}
|
|
334
|
-
|
|
345
|
+
let user = await _searchUser(
|
|
335
346
|
ldapAdminClient,
|
|
336
347
|
userSearchBase,
|
|
337
348
|
usernameAttribute,
|
|
338
349
|
username,
|
|
339
350
|
attributes
|
|
340
351
|
)
|
|
341
|
-
ldapAdminClient.unbind()
|
|
342
352
|
if (!user || !user.dn) {
|
|
343
353
|
ldapOpts.log &&
|
|
344
354
|
ldapOpts.log.trace(
|
|
@@ -349,17 +359,7 @@ async function verifyUserExists(
|
|
|
349
359
|
)
|
|
350
360
|
}
|
|
351
361
|
if (groupsSearchBase && groupClass && groupMemberAttribute) {
|
|
352
|
-
|
|
353
|
-
ldapAdminClient = await _ldapBind(
|
|
354
|
-
adminDn,
|
|
355
|
-
adminPassword,
|
|
356
|
-
starttls,
|
|
357
|
-
ldapOpts
|
|
358
|
-
)
|
|
359
|
-
} catch (error) {
|
|
360
|
-
throw error
|
|
361
|
-
}
|
|
362
|
-
var groups = await _searchUserGroups(
|
|
362
|
+
let groups = await _searchUserGroups(
|
|
363
363
|
ldapAdminClient,
|
|
364
364
|
groupsSearchBase,
|
|
365
365
|
user,
|
|
@@ -368,7 +368,6 @@ async function verifyUserExists(
|
|
|
368
368
|
groupMemberUserAttribute
|
|
369
369
|
)
|
|
370
370
|
user.groups = groups
|
|
371
|
-
ldapAdminClient.unbind()
|
|
372
371
|
}
|
|
373
372
|
return user
|
|
374
373
|
}
|
|
@@ -463,3 +462,9 @@ class LdapAuthenticationError extends Error {
|
|
|
463
462
|
|
|
464
463
|
module.exports.authenticate = authenticate
|
|
465
464
|
module.exports.LdapAuthenticationError = LdapAuthenticationError
|
|
465
|
+
|
|
466
|
+
module.exports.exportForTesting = {
|
|
467
|
+
_isHex,
|
|
468
|
+
_parseEscapedHexToUtf8,
|
|
469
|
+
_recursiveParseHexString,
|
|
470
|
+
}
|
package/package.json
CHANGED
package/test/jasmine.js
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const { exportForTesting } = require('../index.js')
|
|
2
|
+
const { _isHex, _parseEscapedHexToUtf8, _recursiveParseHexString } =
|
|
3
|
+
exportForTesting
|
|
4
|
+
|
|
5
|
+
describe('string conversion test', () => {
|
|
6
|
+
it('unescape string test', () => {
|
|
7
|
+
let s =
|
|
8
|
+
'cn=\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8,ou=users,dc=example,dc=com'
|
|
9
|
+
let us = _parseEscapedHexToUtf8(s)
|
|
10
|
+
expect(us).toEqual('cn=研发A部,ou=users,dc=example,dc=com')
|
|
11
|
+
})
|
|
12
|
+
it('unescape string test2', () => {
|
|
13
|
+
let s =
|
|
14
|
+
'cn=\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8\\c2\\a9,ou=users,dc=example,dc=com'
|
|
15
|
+
let us = _parseEscapedHexToUtf8(s)
|
|
16
|
+
expect(us).toEqual('cn=研发A部©,ou=users,dc=example,dc=com')
|
|
17
|
+
})
|
|
18
|
+
it('unescape string test3', () => {
|
|
19
|
+
let s = 'cn=ABC,ou=users,dc=example,dc=com'
|
|
20
|
+
let us = _parseEscapedHexToUtf8(s)
|
|
21
|
+
expect(us).toEqual('cn=ABC,ou=users,dc=example,dc=com')
|
|
22
|
+
})
|
|
23
|
+
it('convert obj', () => {
|
|
24
|
+
let target = {
|
|
25
|
+
a: ['研发A部©', 'abc'],
|
|
26
|
+
b: 'xyz',
|
|
27
|
+
c: true,
|
|
28
|
+
d: null,
|
|
29
|
+
e: '研发A部©',
|
|
30
|
+
f: 1000,
|
|
31
|
+
}
|
|
32
|
+
let obj = {
|
|
33
|
+
a: ['\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8\\c2\\a9', 'abc'],
|
|
34
|
+
b: 'xyz',
|
|
35
|
+
c: true,
|
|
36
|
+
d: null,
|
|
37
|
+
e: '\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8\\c2\\a9',
|
|
38
|
+
f: 1000,
|
|
39
|
+
}
|
|
40
|
+
let converted = _recursiveParseHexString(obj)
|
|
41
|
+
expect(converted).toEqual(target)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
@@ -124,6 +124,9 @@ describe('ldap-authentication test', () => {
|
|
|
124
124
|
let user = await authenticate(options)
|
|
125
125
|
expect(user).toBeTruthy()
|
|
126
126
|
expect(user.groups.length).toBeGreaterThan(0)
|
|
127
|
+
expect(user.groups[0].objectName).toEqual(
|
|
128
|
+
'cn=科学A部,ou=users,dc=example,dc=com'
|
|
129
|
+
)
|
|
127
130
|
})
|
|
128
131
|
it('Use regular user to authenticate and fetch user group information', async () => {
|
|
129
132
|
let options = {
|
|
@@ -144,6 +147,9 @@ describe('ldap-authentication test', () => {
|
|
|
144
147
|
let user = await authenticate(options)
|
|
145
148
|
expect(user).toBeTruthy()
|
|
146
149
|
expect(user.groups.length).toBeGreaterThan(0)
|
|
150
|
+
expect(user.groups[0].objectName).toEqual(
|
|
151
|
+
'cn=科学A部,ou=users,dc=example,dc=com'
|
|
152
|
+
)
|
|
147
153
|
})
|
|
148
154
|
it('Not specifying groupMemberAttribute or groupMemberUserAttribute should not cause an error and fallback to default values', async () => {
|
|
149
155
|
let options = {
|