Skip to content
This repository was archived by the owner on Sep 6, 2023. It is now read-only.

Commit 27b389d

Browse files
jvohwinkeljvohwinkel
authored andcommitted
Add re-connect and extracted ldap connection to connect per node vs per message.
1 parent 58bd49c commit 27b389d

File tree

3 files changed

+154
-11
lines changed

3 files changed

+154
-11
lines changed

transports/ldap/index.js

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,35 @@
1+
const ldap = require('./ldap');
12
module.exports = function (RED) {
23
'use strict';
34

45
function ldapNode (n) {
56
RED.nodes.createNode(this, n);
7+
let node = this;
68

79
this.options = {
810
host: n.host || 'ldap://localhost',
911
port: n.port || 389
1012
};
13+
14+
node.status({ });
15+
16+
this.connect = function(config, node) {
17+
node.status({ fill:"blue",shape:"dot",text: 'connecting...' });
18+
let url = `${config.options.host}:${config.options.port}`;
19+
ldap.connect(url, config.credentials.username, config.credentials.password).then( (res, err) => {
20+
node.status({ fill: 'green', shape: 'dot', text: 'connected' });
21+
});
22+
};
23+
24+
this.on('close', function (done) {
25+
ldap.disconnect();
26+
node.status({ });
27+
// if (this.tick) { clearTimeout(this.tick); }
28+
// if (this.check) { clearInterval(this.check); }
29+
// node.connected = false;
30+
// node.emit("state"," ");
31+
done();
32+
});
1133
}
1234

1335
function ldapUpdateNode (n) {
@@ -17,27 +39,28 @@ module.exports = function (RED) {
1739
this.attribute = n.attribute;
1840
this.value = n.value;
1941
this.ldapConfig = RED.nodes.getNode(n.ldap);
20-
2142
let node = this;
43+
44+
this.ldapConfig.connect(this.ldapConfig, node);
45+
2246
node.on('input', async function (msg) {
2347
node.operation = msg.operation || node.operation;
2448
node.dn = msg.dn || node.dn;
2549
node.attribute = msg.attribute || node.attribute;
2650
node.value = msg.payload || node.value;
2751

28-
let ldap = require('./ldap');
29-
node.status({ fill:"blue",shape:"dot",text: 'connecting' });
3052
try {
31-
let url = `${node.ldapConfig.options.host}:${node.ldapConfig.options.port}`;
32-
await ldap.connect(url, node.ldapConfig.credentials.username, node.ldapConfig.credentials.password);
33-
node.status({ fill: 'green', shape: 'dot', text: 'connected' });
53+
node.status({ fill: 'blue', shape: 'dot', text: 'running update' });
54+
55+
let update = await ldap.update(node.dn, node.operation, node.attribute, node.value);
56+
msg.ldapStatus = update;
3457

35-
await ldap.update(node.dn, node.operation, node.attribute, node.value);
58+
node.send(msg);
3659

3760
node.status({});
3861
} catch (err) {
3962
node.status({ fill: 'red', shape: 'ring', text: 'failed' });
40-
node.error(err ? err.toString() : 'Unknown error' );
63+
node.error(err ? err : 'Unknown error' );
4164
}
4265
});
4366
}

transports/ldap/ldap.js

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
const ldap = require('ldapjs');
2+
const uuidParse = require('../tools/uuid-parse');
3+
24
module.exports = {
35
client: undefined,
6+
defaultAttributes: {
7+
user: [
8+
'dn', 'userPrincipalName', 'sAMAccountName', 'objectSID', 'mail',
9+
'lockoutTime', 'whenCreated', 'pwdLastSet', 'userAccountControl',
10+
'employeeID', 'sn', 'givenName', 'initials', 'cn', 'displayName',
11+
'comment', 'description', 'title', 'department', 'company'
12+
],
13+
group: [
14+
'dn', 'cn', 'description'
15+
]
16+
},
17+
baseDn: '',
418
/**
19+
* @public
520
* Connect to LDAP server
621
* @param {string} url URL of LDAP server
722
* @param {string} username UPN or dn of user to bind to instance with
@@ -13,16 +28,29 @@ module.exports = {
1328
let that = this;
1429
this.client = ldap.createClient({
1530
url,
31+
reconnect: true,
1632
...options
1733
});
34+
this.client.on('error', function(err) {
35+
console.warn('LDAP connection failed, but fear not, it will reconnect OK', err);
36+
});
1837
return new Promise(function (resolve, reject) {
38+
if (that.client.connected) return resolve();
1939
that.client.bind(username, password, function(err, conn) {
20-
if (err) return reject(err);
40+
if (err) {
41+
return reject(err);
42+
}
2143
return resolve(conn);
2244
});
2345
});
2446
},
47+
disconnect: function () {
48+
if (this.client) {
49+
this.client.unbind();
50+
}
51+
},
2552
/**
53+
* @public
2654
* Perform an update action on a specific LDAP object
2755
* @param {string} dn DN of the object
2856
* @param {string} operation Operation type to perform
@@ -41,12 +69,63 @@ module.exports = {
4169

4270
return new Promise(function (resolve, reject) {
4371
that.client.modify(dn, change, function(err, res) {
44-
console.log('');
4572
if (err) {
73+
4674
return reject({ success: false, error: err });
4775
}
4876
return resolve({ success: true });
4977
});
5078
});
79+
},
80+
/**
81+
* @public
82+
* @param {string} dn base dn for the search
83+
* @param {object} userOpts additional user options for search query
84+
* @returns {Promise<array(object)>}
85+
*/
86+
search: async function (dn, userOpts) {
87+
let opts = {
88+
// filter: '&(dn=CN=Jordan Vohwinkel,OU=Test,OU=Users,OU=NTech,OU=BOE Companies,DC=Corp,DC=BOETeams,DC=com)',
89+
filter: 'cn=Jordan Vohwinkel',
90+
scope: 'sub',
91+
attributes: this.defaultAttributes.user
92+
};
93+
// Overwrite default attributes with user defined
94+
Object.assign(opts, userOpts);
95+
return new Promise(function (resolve, reject) {
96+
this.results = [];
97+
let that = this;
98+
99+
this.client.search(dn, opts, function (err, res) {
100+
if (err) {
101+
console.log(err);
102+
}
103+
res.on('searchEntry', function (entry) {
104+
let res = entry.object;
105+
delete res.controls
106+
107+
this.onSearchEntry(res, entry.raw, function (item) {
108+
that.results.push(item);
109+
})
110+
111+
});
112+
res.on('searchReference', function () { reject('Referral chasing not implemented.') });
113+
res.on('error', function(err) { return reject(err); });
114+
res.on('end', function(result) { return resolve(that.results); });
115+
})
116+
});
117+
},
118+
/**
119+
* @private
120+
* Default search entry parser.
121+
* @param {object} item Item returned from AD
122+
* @param {object} raw Raw return object
123+
* @param {function} callback Callback when parsing is complete
124+
*/
125+
onSearchEntry: function(item, raw, callback) {
126+
if (raw.hasOwnProperty('objectSid')) item.objectSid = uuidParse.unparse(raw.objectSid);
127+
if (raw.hasOwnProperty("objectGUID")) entry.objectGUID = uuidParse.unparse(raw.objectGUID);
128+
if (raw.hasOwnProperty("mS-DS-ConsistencyGuid")) entry['mS-DS-ConsistencyGuid'] = uuidParse.unparse(raw['mS-DS-ConsistencyGuid']);
129+
callback(item);
51130
}
52-
}
131+
};

transports/tools/uuid-parse.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict';
2+
3+
// Maps for number <-> hex string conversion
4+
let _byteToHex = [];
5+
let _hexToByte = {};
6+
for (let i = 0; i < 256; i++) {
7+
_byteToHex[i] = (i + 0x100).toString(16).substr(1);
8+
_hexToByte[_byteToHex[i]] = i;
9+
}
10+
11+
// **`parse()` - Parse a UUID into it's component bytes**
12+
function parse(s, buf, offset) {
13+
let i = (buf && offset) || 0;
14+
let ii = 0;
15+
16+
buf = buf || [];
17+
s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) {
18+
if (ii < 16) { buf[i + ii++] = _hexToByte[oct]; }
19+
});
20+
21+
// Zero out remaining bytes if string was short
22+
while (ii < 16) { buf[i + ii++] = 0; }
23+
24+
return buf;
25+
}
26+
27+
// **`unparse()` - Convert UUID byte array (ala parse()) into a string**
28+
function unparse(buf, offset) {
29+
let i = offset || 0;
30+
let bth = _byteToHex;
31+
return bth[buf[i++]] + bth[buf[i++]] +
32+
bth[buf[i++]] + bth[buf[i++]] + '-' +
33+
bth[buf[i++]] + bth[buf[i++]] + '-' +
34+
bth[buf[i++]] + bth[buf[i++]] + '-' +
35+
bth[buf[i++]] + bth[buf[i++]] + '-' +
36+
bth[buf[i++]] + bth[buf[i++]] +
37+
bth[buf[i++]] + bth[buf[i++]] +
38+
bth[buf[i++]] + bth[buf[i++]];
39+
}
40+
41+
module.exports = { parse, unparse };

0 commit comments

Comments
 (0)