Skip to content
This repository has been archived by the owner on Feb 28, 2021. It is now read-only.

Commit

Permalink
Initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
SirUli committed Oct 16, 2012
1 parent 7f691f3 commit 9e10142
Show file tree
Hide file tree
Showing 2 changed files with 272 additions and 0 deletions.
239 changes: 239 additions & 0 deletions lib/adclient.js
@@ -0,0 +1,239 @@
var ldapjs = require('ldapjs');
var _ = require('lodash');
var _s = require('underscore.string');
_.mixin(_s.exports());

/*
* Parameter Object:
* =================
* params.url:
* - Description: Either a string with one AD-server or an array of ad-servers
* - Example: 'ldap://myldapserver.mycompany.com:389'
*
* params.groupSearchFilter:
* - Description: SearchFilter
* - Example: '(objectclass=*)'
*
* params.groupSearchAttributes:
* - Description: Field used for member identification
* - Example: 'member'
*
* params.groupSearchDN:
* - Description: DN of the group where the members are stored
* - Example: 'CN=MYAPP,OU=Applications,OU=Groups,DC=mycompany,DC=com'
*
* params.masterDn:
* - Description: DN of the master user
* - Example: 'CN=mymaster,OU=IT,DC=mycompany,DC=com'
*
* params.masterPw:
* - Description: Password of the master user
* - Example: 'myv3rys3cr3tp4ssw0rd'
*
* params.userSearchFilter:
* - Description: SearchFilter
* - Example: '(objectclass=*)'
*
* params.userSearchAttributes:
* - Description: Attributes of the user which should be retrieved from AD
* - Example: ['cn', 'distinguishedName', 'sn', 'mail']
*/

function adClient(params) {
this.params = params;
this.masterClient = false;
this.masterBound = false;
this.userClient = false;
this.userBound = false;
}

adClient.prototype.createClients = function(callback) {
var self = this;
self.getUrl(self.params.url, function(err, url) {
if (!err) {
if(self.masterClient == false) {
self.masterClient = ldapjs.createClient({'url': url});
}
if(self.userClient == false) {
self.userClient = ldapjs.createClient({'url': url});
}
callback();
} else {
callback(err);
}
});
}
// Function used for loadbalancing
adClient.prototype.getUrl = function (paramUrl, callback) {
if(_.isArray(paramUrl)) {
// Remove false, null, 0, "", undefined and NaN
paramUrl = _.compact(paramUrl);
// Answer with a random entry
callback(null, paramUrl[_.random(0, paramUrl.length)]);
} else if (_.isString(paramUrl)) {
// Answer with the url
callback(null, paramUrl);
} else {
// No Url available?
callback('No valid url given');
}
}

adClient.prototype.bindMaster = function (callback) {
var self = this
self.createClients(function(err) {
if (err) {callback(err);}
if(self.masterBound) {
callback();
} else {
self.masterClient.bind(self.params.masterDn, self.params.masterPw, callback)
}
});
}

// Closes all connections
adClient.prototype.close = function (callback) {
var self = this
if (self.masterBound) {
self.masterClient.unbind(function(err, result) {
self.masterBound = false;
callback(err, result);
});
}
// If master is not bound
callback();
}

adClient.prototype.extractCNFromDN = function (dn, callback) {
if(! _.isUndefined(dn)) {
_.each(_.words(dn.toLowerCase(), ","), function (dndata) {
if(_.startsWith(dndata, "cn=")) {
callback(null, _.ltrim(dndata, 'cn='))
}
});
} else {
callback('No DN given');
}
}

adClient.prototype.getMembersOfGroupDN = function (callback) {
var self = this
self.bindMaster(function(err) {
if(err) {callback(err);}
var searchParams = {
filter: self.params.groupSearchFilter
, scope: 'sub'
, attributes: self.params.groupSearchAttributes
};
self.masterClient.search(self.params.groupSearchDN, searchParams, function (err, searchResult) {
// Error handling
if (err) {callback(err);}
searchResult.on('error', function (err) {
return callback(err);
});
// for every entry
var resultSet = [];
searchResult.on('searchEntry', function (searchEntry) {
resultSet.push(searchEntry.object);
});
// Finally
searchResult.on('end', function (endResult) {
if (endResult.status !== 0) {
return callback('Status of AD search was' + endResult.status);
}
switch (resultSet.length) {
case 0:
return callback();
case 1:
var memberObjects = [];
if(! _.isUndefined(resultSet[0].member)) {
if(! _.isString(resultSet[0].member)) {
_.each(resultSet[0].member, function(value) {
self.extractCNFromDN(value, function(err, valuedata) {
memberObjects.push({cn: valuedata, dn: value})
});
});
} else {
var value = resultSet[0].member;
self.extractCNFromDN(value, function(err, valuedata) {
memberObjects.push({cn: valuedata, dn: value})
});
}
}
return callback(null, memberObjects)
default:
return callback('Error: unexpected number of matches');
}
});
});
});
}

/*
* Authenticate a user with a DN and a password
*
*/
adClient.prototype.authUserDn = function (userDn, password, callback) {
var self = this;
self.createClients(function(err) {
if (err) {callback(err);}
self.userClient.bind(userDn, password, function(err) {
if (err) {callback(err);}
var searchParams = {
filter: self.params.userSearchFilter
, scope: 'sub'
, attributes: self.params.userSearchAttributes
};
self.userClient.search(userDn, searchParams, function (err, searchResult) {
if (err) {callback(err);}
searchResult.on('error', function (err) {
return callback(err);
});
// for every entry
var resultSet = [];
searchResult.on('searchEntry', function (searchEntry) {
resultSet.push(searchEntry.object);
});
searchResult.on('end', function (endResult) {
if (endResult.status !== 0) {
return callback('Status of AD search was' + endResult.status);
} else {
self.userClient.unbind(function(err) {
switch (resultSet.length) {
case 0:
return callback();
case 1:
// Filter for allowed keys:
return callback(null, _.pick(resultSet[0], self.params.userSearchAttributes));
default:
return callback('Error: unexpected number of matches');
}
});
}
});
});
});
});
}

adClient.prototype.authUser = function (username, password, callback) {
var self = this;
self.getMembersOfGroupDN(function(err, result) {
if (err) {callback(err);}
var user = _.where(result, {cn: username});
if(_.isUndefined(user[0])) {
self.close(function(err) {
callback('Error: User not found');
});
} else {
self.authUserDn(user[0].dn, password, function (err, userObject) {
self.close(function(err2) {
if (err) {callback(err, userObject);}
callback(null, userObject);
});
});
}
});
}

module.exports = adClient;
33 changes: 33 additions & 0 deletions package.json
@@ -0,0 +1,33 @@
{
"author": {
"name": "Uli Wolf",
"email": "github@wolf-u.li"
},
"name": "adclient",
"description": "ldapjs client for authentication with active directory",
"version": "0.0.1",
"repository": {
"type": "git",
"url": "git://github.com/SirUli/node-adclient.git"
},
"keywords": [
"ldap",
"ldapjs",
"active directory"
],
"bugs": {
"url": "https://github.com/SirUli/node-adclient/issues"
},
"main": "lib/adclient.js",
"directories": {
"lib": "./lib"
},
"engines": {
"node": ">=0.8"
},
"dependencies": {
"lodash": "*"
, "ldapjs": "*"
, "underscore.string": "*"
}
}

0 comments on commit 9e10142

Please sign in to comment.