2018 LDAP(2)NodeJS and LDAP
Set up LDAP Server
Install LDAPJS
> npm install ldapjs
I suppose to have OpenLDAP CLI on my machine, check it out.
> ldapsearch -H
> ldapsearch -V
ldapsearch: @(#) $OpenLDAP: ldapsearch 2.4.28 (Jul 15 2017 15:44:01) $
Set Up a Simple Project as follow: package.json
{
"devDependencies": {
"gulp": "3.9.0",
"gulp-jshint": "1.12.0",
"gulp-mocha": "2.1.3",
"gulp-nodemon": "2.0.4",
"gulp-util": "3.0.7",
"gulp-concat": "2.6.0",
"gulp-tar": "1.5.0",
"gulp-gzip": "1.2.0",
"gulp-artifactory-upload": "1.2.0",
"gulp-jsdoc": "0.1.5",
"gulp-stubby-server": "0.1.5",
"gulp-help": "1.6.1",
"ldapjs": "1.0.2"
},
"engines": {
"node": ">=0.12.0"
},
"name": "ldap-server",
"private": true,
"version": "1.0.0"
}
password-app.js
/*jslint node: true */
'use strict';
var ldap = require('ldapjs');
var server = ldap.createServer();
server.listen(1389, function(){
console.log('/etc/passwd LDAP server up at: %s', server.url);
});
NPM install and run with command
> node password-app.js
Run the client to connect to that
> ldapsearch -H ldap://localhost:1389 -x -b "o=myhost" objectclass=*
# extended LDIF
#
# LDAPv3
# base with scope subtree
# filter: objectclass=*
# requesting: ALL
#
# search result
search: 2
result: 32 No such object
text: No tree found for: o=myhost
# numResponses: 1
Add this binding to the server
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();
});
We will have the password to verified.
> 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: InvalidCredentialsError
> 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
The format for /etc/passwd is as follow:
jsmith:x:1001:1000:Joe Smith,Room 1007,(234)555-8910,(234)555-0044,email:/home/jsmith:/bin/sh
jsmith - Username
x - password hash
1001 - Numeric UID
1000 - Numeric Primary GID
‘Joe Smith, …’ - DisplayName and Other information
/home/jsmith. Home directory
/bin/sh Shell
Load and Parse all the Data into req.users
function loadPasswdFile(req, res, next) {
fs.readFile('/etc/passwd', 'utf8', function(err, data) {
if (err)
return next(new ldap.OperationsError(err.message));
req.users = {};
var lines = data.split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i] || /^#/.test(lines[i])) // empty line or start with #, ignore
continue;
var record = lines[i].split(':');
if (!record || !record.length) //no :, ignore
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();
});
}
Add this to the binding
var pre = [authorize, loadPasswdFile];
server.search('o=myhost', pre, function(req, res, next) {
Object.keys(req.users).forEach(function(k) {
if (req.filter.matches(req.users[k].attributes))
res.send(req.users[k]);
});
res.end();
return next();
});
Search for root user
> 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
List all the user information
> ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" objectclass=*
CN start with r*
> ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" cn=r*
dn: cn=root, ou=users, o=myhost
cn: root
uid: 0
gid: 0
description: System Administrator
homedirectory: /var/root
shell: /bin/sh
objectclass: unixUser
Server Add
server.add('ou=users, o=myhost', pre, function(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()));
var entry = req.toObject().attributes;
if (entry.objectclass.indexOf('unixUser') === -1)
return next(new ldap.ConstraintViolation('entry must be a unixUser'));
var 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]);
var useradd = spawn('useradd', opts);
var messages = [];
useradd.stdout.on('data', function(data) {
messages.push(data.toString());
});
useradd.stderr.on('data', function(data) {
messages.push(data.toString());
});
useradd.on('exit', function(code) {
if (code !== 0) {
var msg = '' + code;
if (messages.length)
msg += ': ' + messages.join();
return next(new ldap.OperationsError(msg));
}
res.end();
return next();
});
});
Execute the command useradd
Modify User
server.modify('ou=users, o=myhost', pre, function(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'));
var user = req.users[req.dn.rdns[0].cn].attributes;
var mod;
for (var 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'));
}
}
var passwd = spawn('chpasswd', ['-c', 'MD5']);
passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8');
passwd.on('exit', function(code) {
if (code !== 0)
return next(new ldap.OperationsError('' + code));
res.end();
return next();
});
});
Create a file user.ldif
dn: cn=ldapjs, ou=users, o=myhost
objectClass: unixUser
cn: ldapjs
shell: /bin/bash
description: Created via ldapadd
Add User
>ldapadd -H ldap://localhost:1389 -x -D cn=root -w secret -f ./user.ldif
Change Password passwd.ldif
dn: cn=ldapjs, ou=users, o=myhost
changetype: modify
replace: userPassword
userPassword: secret
>ldapmodify -H ldap://localhost:1389 -x -D cn=root -w secret -f ./passwd.ldif
Delete User
>ldapdelete -H ldap://localhost:1389 -x -D cn=root -w secret "cn=ldapjs, ou=users, o=myhost"
It seems that the ldapJS will handle the protocol things, everything under that, our solution can store the data in DB or file system.
References:
http://ldapjs.org/guide.html
http://sillycat.iteye.com/blog/2414080
http://ldapjs.org/examples.html