Skip to content

Commit

Permalink
handle utf8 encoded string from ldap (#66)
Browse files Browse the repository at this point in the history
* handle utf8 encoded string from ldap

* add utf8 group to ldap testing container
  • Loading branch information
shaozi authored Jun 20, 2024
1 parent 4b2b980 commit 3c25589
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 5 deletions.
1 change: 1 addition & 0 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ services:
- LDAP_ADMIN_PASSWORD=password
- LDAP_USERS=gauss,einstein
- LDAP_PASSWORDS=password,password
- LDAP_GROUP=科学A部
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ services:
- LDAP_ADMIN_PASSWORD=password
- LDAP_USERS=gauss,einstein
- LDAP_PASSWORDS=password,password
- LDAP_GROUP=科学A部
50 changes: 48 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,46 @@
const assert = require('assert')
const ldap = require('ldapjs')

// convert an escaped utf8 string returned from ldapjs
function _isHex(c) {
return (
(c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
)
}
function _parseEscapedHexToUtf8(s) {
// convert 'cn=\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8,ou=users,dc=example,dc=com'
// to 'cn=研发A部,ou=users,dc=example,dc=com'
let ret = Buffer.alloc(0)
let len = s.length
for (let i = 0; i < len; i++) {
let c = s[i]
let item
if (c == '\\' && i < len - 2 && _isHex(s[i + 1]) && _isHex(s[i + 2])) {
item = Buffer.from(s.substring(i + 1, i + 3), 'hex')
i += 2
} else {
item = Buffer.from(c)
}
ret = Buffer.concat([ret, item])
}
return ret.toString()
}

function _recursiveParseHexString(obj) {
if (Array.isArray(obj)) {
return obj.map((ele) => _recursiveParseHexString(ele))
}
if (typeof obj == 'string') {
return _parseEscapedHexToUtf8(obj)
}
if (typeof obj == 'object') {
for (let key in obj) {
obj[key] = _recursiveParseHexString(obj[key])
}
return obj
}
return obj
}
// convert a SearchResultEntry object in ldapjs 3.0
// to a user object to maintain backward compatibility

Expand All @@ -11,7 +51,7 @@ function _searchResultToUser(pojo) {
user[attribute.type] =
attribute.values.length == 1 ? attribute.values[0] : attribute.values
})
return user
return _recursiveParseHexString(user)
}
// bind and return the ldap client
function _ldapBind(dn, password, starttls, ldapOpts) {
Expand Down Expand Up @@ -142,7 +182,7 @@ async function _searchUserGroups(
return
}
res.on('searchEntry', function (entry) {
groups.push(entry.pojo)
groups.push(_recursiveParseHexString(entry.pojo))
})
res.on('searchReference', function (referral) {})
res.on('error', function (err) {
Expand Down Expand Up @@ -422,3 +462,9 @@ class LdapAuthenticationError extends Error {

module.exports.authenticate = authenticate
module.exports.LdapAuthenticationError = LdapAuthenticationError

module.exports.exportForTesting = {
_isHex,
_parseEscapedHexToUtf8,
_recursiveParseHexString,
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ldap-authentication",
"version": "3.2.1",
"version": "3.2.2",
"description": "A simple async nodejs library for LDAP user authentication",
"main": "index.js",
"types": "./index.d.ts",
Expand Down
43 changes: 43 additions & 0 deletions test/string.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const { exportForTesting } = require('../index.js')
const { _isHex, _parseEscapedHexToUtf8, _recursiveParseHexString } =
exportForTesting

describe('string conversion test', () => {
it('unescape string test', () => {
let s =
'cn=\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8,ou=users,dc=example,dc=com'
let us = _parseEscapedHexToUtf8(s)
expect(us).toEqual('cn=研发A部,ou=users,dc=example,dc=com')
})
it('unescape string test2', () => {
let s =
'cn=\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8\\c2\\a9,ou=users,dc=example,dc=com'
let us = _parseEscapedHexToUtf8(s)
expect(us).toEqual('cn=研发A部©,ou=users,dc=example,dc=com')
})
it('unescape string test3', () => {
let s = 'cn=ABC,ou=users,dc=example,dc=com'
let us = _parseEscapedHexToUtf8(s)
expect(us).toEqual('cn=ABC,ou=users,dc=example,dc=com')
})
it('convert obj', () => {
let target = {
a: ['研发A部©', 'abc'],
b: 'xyz',
c: true,
d: null,
e: '研发A部©',
f: 1000,
}
let obj = {
a: ['\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8\\c2\\a9', 'abc'],
b: 'xyz',
c: true,
d: null,
e: '\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8\\c2\\a9',
f: 1000,
}
let converted = _recursiveParseHexString(obj)
expect(converted).toEqual(target)
})
})
6 changes: 6 additions & 0 deletions test/test.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ describe('ldap-authentication test', () => {
let user = await authenticate(options)
expect(user).toBeTruthy()
expect(user.groups.length).toBeGreaterThan(0)
expect(user.groups[0].objectName).toEqual(
'cn=科学A部,ou=users,dc=example,dc=com'
)
})
it('Use regular user to authenticate and fetch user group information', async () => {
let options = {
Expand All @@ -144,6 +147,9 @@ describe('ldap-authentication test', () => {
let user = await authenticate(options)
expect(user).toBeTruthy()
expect(user.groups.length).toBeGreaterThan(0)
expect(user.groups[0].objectName).toEqual(
'cn=科学A部,ou=users,dc=example,dc=com'
)
})
it('Not specifying groupMemberAttribute or groupMemberUserAttribute should not cause an error and fallback to default values', async () => {
let options = {
Expand Down

0 comments on commit 3c25589

Please sign in to comment.