Documentation and makefile

This commit is contained in:
Mark Cavage 2011-08-15 10:53:57 -07:00
parent e87550ff57
commit 17d2d4e5cb
4 changed files with 242 additions and 176 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
node_modules node_modules
*.log *.log
*.ldif *.ldif
*.tar*
docs/pkg

65
Makefile Normal file
View File

@ -0,0 +1,65 @@
NAME=ldapjs
ifeq ($(VERSION), "")
@echo "Use gmake"
endif
SRC := $(shell pwd)
TAR = tar
UNAME := $(shell uname)
ifeq ($(UNAME), SunOS)
TAR = gtar
endif
HAVE_GJSLINT := $(shell which gjslint >/dev/null && echo yes || echo no)
NPM := npm_config_tar=$(TAR) npm
RESTDOWN = ./node_modules/.restdown/bin/restdown
RESTDOWN_VERSION=1.2.11
DOCPKGDIR = ./docs/pkg
.PHONY: dep lint test doc clean all
all:: test doc
node_modules/.ldapjs.npm.installed:
$(NPM) install --dev
if [[ ! -d node_modules/.restdown ]]; then \
git clone git://github.com/trentm/restdown.git node_modules/.restdown; \
else \
(cd node_modules/.restdown && git fetch origin); \
fi
@(cd ./node_modules/.restdown && git checkout $(RESTDOWN_VERSION))
@touch ./node_modules/.ldapjs.npm.installed
dep: ./node_modules/.ldapjs.npm.installed
gjslint:
gjslint --nojsdoc -r lib -r tst
ifeq ($(HAVE_GJSLINT), yes)
lint: gjslint
else
lint:
@echo "* * *"
@echo "* Warning: Cannot lint with gjslint. Install it from:"
@echo "* http://code.google.com/closure/utilities/docs/linter_howto.html"
@echo "* * *"
endif
doc: dep
@rm -rf ${DOCPKGDIR}
@mkdir -p ${DOCPKGDIR}
${RESTDOWN} -m ${DOCPKGDIR} -D mediaroot=media ./docs/guide.md
rm docs/*.json
mv docs/*.html ${DOCPKGDIR}
sed -i '' -e 's|docs/public/media|media|g' ${DOCPKGDIR}/*.html
(cd ${DOCPKGDIR} && $(TAR) -czf ${SRC}/${NAME}-docs-`git log -1 --pretty='format:%h'`.tar.gz *)
test: dep lint
$(NPM) test
clean:
@rm -fr ${DOCPKGDIR} node_modules *.log

View File

@ -28,12 +28,12 @@ basically breaks down as follows:
It might be helpful to visualize that: It might be helpful to visualize that:
o=example o=example
/ \ / \
ou=users ou=groups ou=users ou=groups
/ | | \ / | | \
cn=john cn=jane cn=dudes cn=dudettes cn=john cn=jane cn=dudes cn=dudettes
/ /
keyid=foo keyid=foo
@ -187,8 +187,8 @@ Blah blah, let's try running the ldap client again, first with a bad password:
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w foo -b "o=myhost" objectclass=* $ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w foo -b "o=myhost" objectclass=*
ldap_bind: Invalid credentials (49) ldap_bind: Invalid credentials (49)
matched DN: cn=root matched DN: cn=root
additional info: Invalid Credentials additional info: Invalid Credentials
And again with the correct one: And again with the correct one:
@ -212,7 +212,7 @@ authorization handler that we'll use in all our subsequent routes:
function authorize(req, res, next) { function authorize(req, res, next) {
if (!req.connection.ldap.bindDN.equals('cn=root')) if (!req.connection.ldap.bindDN.equals('cn=root'))
return next(new ldap.InsufficientAccessRightsError()); return next(new ldap.InsufficientAccessRightsError());
return next(); return next();
} }
@ -249,35 +249,35 @@ First, let's make a handler that just loads the "user database" for us in a
function loadPasswdFile(req, res, next) { function loadPasswdFile(req, res, next) {
fs.readFile('/etc/passwd', 'utf8', function(err, data) { fs.readFile('/etc/passwd', 'utf8', function(err, data) {
if (err) if (err)
return next(new ldap.OperationsError(err.message)); return next(new ldap.OperationsError(err.message));
req.users = {}; req.users = {};
var lines = data.split('\n'); var lines = data.split('\n');
for (var i = 0; i < lines.length; i++) { for (var i = 0; i < lines.length; i++) {
if (!lines[i] || /^#/.test(lines[i])) if (!lines[i] || /^#/.test(lines[i]))
continue; continue;
var record = lines[i].split(':'); var record = lines[i].split(':');
if (!record || !record.length) if (!record || !record.length)
continue; continue;
req.users[record[0]] = { req.users[record[0]] = {
dn: 'cn=' + record[0] + ', ou=users, o=myhost', dn: 'cn=' + record[0] + ', ou=users, o=myhost',
attributes: { attributes: {
cn: record[0], cn: record[0],
uid: record[2], uid: record[2],
gid: record[3], gid: record[3],
description: record[4], description: record[4],
homedirectory: record[5], homedirectory: record[5],
shell: record[6] || '', shell: record[6] || '',
objectclass: 'unixUser' objectclass: 'unixUser'
} }
}; };
} }
return next(); return next();
}); });
} }
@ -289,8 +289,8 @@ handler to process that:
server.search('o=myhost', pre, function(req, res, next) { server.search('o=myhost', pre, function(req, res, next) {
Object.keys(req.users).forEach(function(k) { Object.keys(req.users).forEach(function(k) {
if (req.filter.matches(req.users[k].attributes)) if (req.filter.matches(req.users[k].attributes))
res.send(req.users[k]); res.send(req.users[k]);
}); });
res.end(); res.end();
@ -357,13 +357,13 @@ of attributes. So that's why we did this:
var entry = { var entry = {
dn: 'cn=' + record[0] + ', ou=users, o=myhost', dn: 'cn=' + record[0] + ', ou=users, o=myhost',
attributes: { attributes: {
cn: record[0], cn: record[0],
uid: record[2], uid: record[2],
gid: record[3], gid: record[3],
description: record[4], description: record[4],
homedirectory: record[5], homedirectory: record[5],
shell: record[6] || '', shell: record[6] || '',
objectclass: 'unixUser' objectclass: 'unixUser'
} }
}; };
@ -398,36 +398,36 @@ the following code in as another handler (you'll need a
server.add('ou=users, o=myhost', pre, function(req, res, next) { server.add('ou=users, o=myhost', pre, function(req, res, next) {
if (!req.dn.rdns[0].cn) if (!req.dn.rdns[0].cn)
return next(new ldap.ConstraintViolationError('cn required')); return next(new ldap.ConstraintViolationError('cn required'));
if (req.users[req.dn.rdns[0].cn]) if (req.users[req.dn.rdns[0].cn])
return next(new ldap.EntryAlreadyExistsError(req.dn.toString())); return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
var entry = req.toObject().attributes; var entry = req.toObject().attributes;
if (entry.objectclass.indexOf('unixUser') === -1) if (entry.objectclass.indexOf('unixUser') === -1)
return next(new ldap.ConstraintViolation('entry must be a unixUser')); return next(new ldap.ConstraintViolation('entry must be a unixUser'));
var opts = ['-m']; var opts = ['-m'];
if (entry.description) { if (entry.description) {
opts.push('-c'); opts.push('-c');
opts.push(entry.description[0]); opts.push(entry.description[0]);
} }
if (entry.homedirectory) { if (entry.homedirectory) {
opts.push('-d'); opts.push('-d');
opts.push(entry.homedirectory[0]); opts.push(entry.homedirectory[0]);
} }
if (entry.gid) { if (entry.gid) {
opts.push('-g'); opts.push('-g');
opts.push(entry.gid[0]); opts.push(entry.gid[0]);
} }
if (entry.shell) { if (entry.shell) {
opts.push('-s'); opts.push('-s');
opts.push(entry.shell[0]); opts.push(entry.shell[0]);
} }
if (entry.uid) { if (entry.uid) {
opts.push('-u'); opts.push('-u');
opts.push(entry.uid[0]); opts.push(entry.uid[0]);
} }
opts.push(entry.cn[0]); opts.push(entry.cn[0]);
var useradd = spawn('useradd', opts); var useradd = spawn('useradd', opts);
@ -435,22 +435,22 @@ the following code in as another handler (you'll need a
var messages = []; var messages = [];
useradd.stdout.on('data', function(data) { useradd.stdout.on('data', function(data) {
messages.push(data.toString()); messages.push(data.toString());
}); });
useradd.stderr.on('data', function(data) { useradd.stderr.on('data', function(data) {
messages.push(data.toString()); messages.push(data.toString());
}); });
useradd.on('exit', function(code) { useradd.on('exit', function(code) {
if (code !== 0) { if (code !== 0) {
var msg = '' + code; var msg = '' + code;
if (messages.length) if (messages.length)
msg += ': ' + messages.join(); msg += ': ' + messages.join();
return next(new ldap.OperationsError(msg)); return next(new ldap.OperationsError(msg));
} }
res.end(); res.end();
return next(); return next();
}); });
}); });
@ -485,15 +485,15 @@ As before, here's a breakdown of the code:
server.add('ou=users, o=myhost', pre, function(req, res, next) { server.add('ou=users, o=myhost', pre, function(req, res, next) {
if (!req.dn.rdns[0].cn) if (!req.dn.rdns[0].cn)
return next(new ldap.ConstraintViolationError('cn required')); return next(new ldap.ConstraintViolationError('cn required'));
if (req.users[req.dn.rdns[0].cn]) if (req.users[req.dn.rdns[0].cn])
return next(new ldap.EntryAlreadyExistsError(req.dn.toString())); return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
var entry = req.toObject().attributes; var entry = req.toObject().attributes;
if (entry.objectclass.indexOf('unixUser') === -1) if (entry.objectclass.indexOf('unixUser') === -1)
return next(new ldap.ConstraintViolation('entry must be a unixUser')); return next(new ldap.ConstraintViolation('entry must be a unixUser'));
Here's a few new things: Here's a few new things:
@ -530,37 +530,37 @@ Go ahead and add the following code into your source file:
server.modify('ou=users, o=myhost', pre, function(req, res, next) { server.modify('ou=users, o=myhost', pre, function(req, res, next) {
if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn]) if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn])
return next(new ldap.NoSuchObjectError(req.dn.toString())); return next(new ldap.NoSuchObjectError(req.dn.toString()));
if (!req.changes.length) if (!req.changes.length)
return next(new ldap.ProtocolError('changes required')); return next(new ldap.ProtocolError('changes required'));
var user = req.users[req.dn.rdns[0].cn].attributes; var user = req.users[req.dn.rdns[0].cn].attributes;
var mod; var mod;
for (var i = 0; i < req.changes.length; i++) { for (var i = 0; i < req.changes.length; i++) {
mod = req.changes[i].modification; mod = req.changes[i].modification;
switch (req.changes[i].operation) { switch (req.changes[i].operation) {
case 'replace': case 'replace':
if (mod.type !== 'userpassword' || !mod.vals || !mod.vals.length) if (mod.type !== 'userpassword' || !mod.vals || !mod.vals.length)
return next(new ldap.UnwillingToPerformError('only password updates ' + return next(new ldap.UnwillingToPerformError('only password updates ' +
'allowed')); 'allowed'));
break; break;
case 'add': case 'add':
case 'delete': case 'delete':
return next(new ldap.UnwillingToPerformError('only replace allowed')); return next(new ldap.UnwillingToPerformError('only replace allowed'));
} }
} }
var passwd = spawn('chpasswd', ['-c', 'MD5']); var passwd = spawn('chpasswd', ['-c', 'MD5']);
passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8'); passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8');
passwd.on('exit', function(code) { passwd.on('exit', function(code) {
if (code !== 0) if (code !== 0)
return next(new ldap.OperationsError(code)); return next(new ldap.OperationsError(code));
res.end(); res.end();
return next(); return next();
}); });
}); });
@ -592,28 +592,28 @@ delete it :). Go ahead and add the following code into your server:
server.del('ou=users, o=myhost', pre, function(req, res, next) { server.del('ou=users, o=myhost', pre, function(req, res, next) {
if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn]) if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn])
return next(new ldap.NoSuchObjectError(req.dn.toString())); return next(new ldap.NoSuchObjectError(req.dn.toString()));
var userdel = spawn('userdel', ['-f', req.dn.rdns[0].cn]); var userdel = spawn('userdel', ['-f', req.dn.rdns[0].cn]);
var messages = []; var messages = [];
userdel.stdout.on('data', function(data) { userdel.stdout.on('data', function(data) {
messages.push(data.toString()); messages.push(data.toString());
}); });
userdel.stderr.on('data', function(data) { userdel.stderr.on('data', function(data) {
messages.push(data.toString()); messages.push(data.toString());
}); });
userdel.on('exit', function(code) { userdel.on('exit', function(code) {
if (code !== 0) { if (code !== 0) {
var msg = '' + code; var msg = '' + code;
if (messages.length) if (messages.length)
msg += ': ' + messages.join(); msg += ': ' + messages.join();
return next(new ldap.OperationsError(msg)); return next(new ldap.OperationsError(msg));
} }
res.end(); res.end();
return next(); return next();
}); });
}); });
@ -638,7 +638,7 @@ the complete implementation for what we went through above:
function authorize(req, res, next) { function authorize(req, res, next) {
if (!req.connection.ldap.bindDN.equals('cn=root')) if (!req.connection.ldap.bindDN.equals('cn=root'))
return next(new ldap.InsufficientAccessRightsError()); return next(new ldap.InsufficientAccessRightsError());
return next(); return next();
} }
@ -646,35 +646,35 @@ the complete implementation for what we went through above:
function loadPasswdFile(req, res, next) { function loadPasswdFile(req, res, next) {
fs.readFile('/etc/passwd', 'utf8', function(err, data) { fs.readFile('/etc/passwd', 'utf8', function(err, data) {
if (err) if (err)
return next(new ldap.OperationsError(err.message)); return next(new ldap.OperationsError(err.message));
req.users = {}; req.users = {};
var lines = data.split('\n'); var lines = data.split('\n');
for (var i = 0; i < lines.length; i++) { for (var i = 0; i < lines.length; i++) {
if (!lines[i] || /^#/.test(lines[i])) if (!lines[i] || /^#/.test(lines[i]))
continue; continue;
var record = lines[i].split(':'); var record = lines[i].split(':');
if (!record || !record.length) if (!record || !record.length)
continue; continue;
req.users[record[0]] = { req.users[record[0]] = {
dn: 'cn=' + record[0] + ', ou=users, o=myhost', dn: 'cn=' + record[0] + ', ou=users, o=myhost',
attributes: { attributes: {
cn: record[0], cn: record[0],
uid: record[2], uid: record[2],
gid: record[3], gid: record[3],
description: record[4], description: record[4],
homedirectory: record[5], homedirectory: record[5],
shell: record[6] || '', shell: record[6] || '',
objectclass: 'unixUser' objectclass: 'unixUser'
} }
}; };
} }
return next(); return next();
}); });
} }
@ -689,7 +689,7 @@ the complete implementation for what we went through above:
server.bind('cn=root', function(req, res, next) { server.bind('cn=root', function(req, res, next) {
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret') if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
return next(new ldap.InvalidCredentialsError()); return next(new ldap.InvalidCredentialsError());
res.end(); res.end();
return next(); return next();
@ -698,36 +698,36 @@ the complete implementation for what we went through above:
server.add('ou=users, o=myhost', pre, function(req, res, next) { server.add('ou=users, o=myhost', pre, function(req, res, next) {
if (!req.dn.rdns[0].cn) if (!req.dn.rdns[0].cn)
return next(new ldap.ConstraintViolationError('cn required')); return next(new ldap.ConstraintViolationError('cn required'));
if (req.users[req.dn.rdns[0].cn]) if (req.users[req.dn.rdns[0].cn])
return next(new ldap.EntryAlreadyExistsError(req.dn.toString())); return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
var entry = req.toObject().attributes; var entry = req.toObject().attributes;
if (entry.objectclass.indexOf('unixUser') === -1) if (entry.objectclass.indexOf('unixUser') === -1)
return next(new ldap.ConstraintViolation('entry must be a unixUser')); return next(new ldap.ConstraintViolation('entry must be a unixUser'));
var opts = ['-m']; var opts = ['-m'];
if (entry.description) { if (entry.description) {
opts.push('-c'); opts.push('-c');
opts.push(entry.description[0]); opts.push(entry.description[0]);
} }
if (entry.homedirectory) { if (entry.homedirectory) {
opts.push('-d'); opts.push('-d');
opts.push(entry.homedirectory[0]); opts.push(entry.homedirectory[0]);
} }
if (entry.gid) { if (entry.gid) {
opts.push('-g'); opts.push('-g');
opts.push(entry.gid[0]); opts.push(entry.gid[0]);
} }
if (entry.shell) { if (entry.shell) {
opts.push('-s'); opts.push('-s');
opts.push(entry.shell[0]); opts.push(entry.shell[0]);
} }
if (entry.uid) { if (entry.uid) {
opts.push('-u'); opts.push('-u');
opts.push(entry.uid[0]); opts.push(entry.uid[0]);
} }
opts.push(entry.cn[0]); opts.push(entry.cn[0]);
var useradd = spawn('useradd', opts); var useradd = spawn('useradd', opts);
@ -735,95 +735,95 @@ the complete implementation for what we went through above:
var messages = []; var messages = [];
useradd.stdout.on('data', function(data) { useradd.stdout.on('data', function(data) {
messages.push(data.toString()); messages.push(data.toString());
}); });
useradd.stderr.on('data', function(data) { useradd.stderr.on('data', function(data) {
messages.push(data.toString()); messages.push(data.toString());
}); });
useradd.on('exit', function(code) { useradd.on('exit', function(code) {
if (code !== 0) { if (code !== 0) {
var msg = '' + code; var msg = '' + code;
if (messages.length) if (messages.length)
msg += ': ' + messages.join(); msg += ': ' + messages.join();
return next(new ldap.OperationsError(msg)); return next(new ldap.OperationsError(msg));
} }
res.end(); res.end();
return next(); return next();
}); });
}); });
server.modify('ou=users, o=myhost', pre, function(req, res, next) { server.modify('ou=users, o=myhost', pre, function(req, res, next) {
if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn]) if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn])
return next(new ldap.NoSuchObjectError(req.dn.toString())); return next(new ldap.NoSuchObjectError(req.dn.toString()));
if (!req.changes.length) if (!req.changes.length)
return next(new ldap.ProtocolError('changes required')); return next(new ldap.ProtocolError('changes required'));
var user = req.users[req.dn.rdns[0].cn].attributes; var user = req.users[req.dn.rdns[0].cn].attributes;
var mod; var mod;
for (var i = 0; i < req.changes.length; i++) { for (var i = 0; i < req.changes.length; i++) {
mod = req.changes[i].modification; mod = req.changes[i].modification;
switch (req.changes[i].operation) { switch (req.changes[i].operation) {
case 'replace': case 'replace':
if (mod.type !== 'userpassword' || !mod.vals || !mod.vals.length) if (mod.type !== 'userpassword' || !mod.vals || !mod.vals.length)
return next(new ldap.UnwillingToPerformError('only password updates ' + return next(new ldap.UnwillingToPerformError('only password updates ' +
'allowed')); 'allowed'));
break; break;
case 'add': case 'add':
case 'delete': case 'delete':
return next(new ldap.UnwillingToPerformError('only replace allowed')); return next(new ldap.UnwillingToPerformError('only replace allowed'));
} }
} }
var passwd = spawn('chpasswd', ['-c', 'MD5']); var passwd = spawn('chpasswd', ['-c', 'MD5']);
passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8'); passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8');
passwd.on('exit', function(code) { passwd.on('exit', function(code) {
if (code !== 0) if (code !== 0)
return next(new ldap.OperationsError('' + code)); return next(new ldap.OperationsError('' + code));
res.end(); res.end();
return next(); return next();
}); });
}); });
server.del('ou=users, o=myhost', pre, function(req, res, next) { server.del('ou=users, o=myhost', pre, function(req, res, next) {
if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn]) if (!req.dn.rdns[0].cn || !req.users[req.dn.rdns[0].cn])
return next(new ldap.NoSuchObjectError(req.dn.toString())); return next(new ldap.NoSuchObjectError(req.dn.toString()));
var userdel = spawn('userdel', ['-f', req.dn.rdns[0].cn]); var userdel = spawn('userdel', ['-f', req.dn.rdns[0].cn]);
var messages = []; var messages = [];
userdel.stdout.on('data', function(data) { userdel.stdout.on('data', function(data) {
messages.push(data.toString()); messages.push(data.toString());
}); });
userdel.stderr.on('data', function(data) { userdel.stderr.on('data', function(data) {
messages.push(data.toString()); messages.push(data.toString());
}); });
userdel.on('exit', function(code) { userdel.on('exit', function(code) {
if (code !== 0) { if (code !== 0) {
var msg = '' + code; var msg = '' + code;
if (messages.length) if (messages.length)
msg += ': ' + messages.join(); msg += ': ' + messages.join();
return next(new ldap.OperationsError(msg)); return next(new ldap.OperationsError(msg));
} }
res.end(); res.end();
return next(); return next();
}); });
}); });
server.search('o=myhost', pre, function(req, res, next) { server.search('o=myhost', pre, function(req, res, next) {
Object.keys(req.users).forEach(function(k) { Object.keys(req.users).forEach(function(k) {
if (req.filter.matches(req.users[k].attributes)) if (req.filter.matches(req.users[k].attributes))
res.send(req.users[k]); res.send(req.users[k]);
}); });
res.end(); res.end();

View File

@ -22,7 +22,6 @@
"node-uuid": "~1.2.0" "node-uuid": "~1.2.0"
}, },
"scripts": { "scripts": {
"pretest": "which gjslint; if [[ \"$?\" = 0 ]] ; then gjslint --nojsdoc -r lib -r tst; else echo \"Missing gjslint. Skipping lint\"; fi",
"test": "./node_modules/.bin/tap ./tst" "test": "./node_modules/.bin/tap ./tst"
} }
} }