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 +114 -122
- package/package.json +3 -3
- package/test/string.spec.js +20 -1
- package/test/test.spec.js +3 -3
package/index.js
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
const assert = require('assert')
|
|
2
|
-
const
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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.
|
|
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
|
-
"
|
|
39
|
+
"ldapts": "^7.3.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"jasmine": "^5.
|
|
42
|
+
"jasmine": "^5.6.0"
|
|
43
43
|
}
|
|
44
44
|
}
|
package/test/string.spec.js
CHANGED
|
@@ -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].
|
|
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
|
)
|